Files
metabase/e2e/test/scenarios/sharing/alert/alert.cy.spec.js
Chris Vandoodewaard 519834f9ee Consolidate sharing e2e tests (#83657)
* Remove duplicate public sharing e2e tests

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011Jc2P2sKm39MghfKPxCSiv

* Merge sharing reproductions e2e tests that share setup

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011Jc2P2sKm39MghfKPxCSiv

* Merge downloads e2e tests that share setup

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011Jc2P2sKm39MghfKPxCSiv

* Merge public sharing e2e tests that share setup

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011Jc2P2sKm39MghfKPxCSiv

* Repair downloads e2e tests that cannot fail

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011Jc2P2sKm39MghfKPxCSiv

* Repair sharing reproductions e2e tests that cannot fail

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011Jc2P2sKm39MghfKPxCSiv

* Drop unused setup in sharing reproductions e2e tests

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011Jc2P2sKm39MghfKPxCSiv

* Remove duplicate subscriptions e2e tests

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011Jc2P2sKm39MghfKPxCSiv

* Repair public sharing e2e tests that cannot fail

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011Jc2P2sKm39MghfKPxCSiv

* Drop unused setup in downloads e2e tests

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011Jc2P2sKm39MghfKPxCSiv

* Drop unused setup in public sharing e2e tests

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011Jc2P2sKm39MghfKPxCSiv

* Remove duplicate alert e2e tests

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011Jc2P2sKm39MghfKPxCSiv

* Merge alert e2e tests that share setup

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011Jc2P2sKm39MghfKPxCSiv

* Repair alert e2e tests that cannot fail

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011Jc2P2sKm39MghfKPxCSiv

* Drop unused setup in alert e2e tests

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011Jc2P2sKm39MghfKPxCSiv

* Remove duplicate public sharing e2e tests

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011Jc2P2sKm39MghfKPxCSiv

* Merge subscriptions e2e tests that share setup

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011Jc2P2sKm39MghfKPxCSiv

* Repair subscriptions e2e tests that cannot fail

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011Jc2P2sKm39MghfKPxCSiv

* Drop unused setup in subscriptions e2e tests

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011Jc2P2sKm39MghfKPxCSiv

* Fold public dashboard link creation e2e test into 20393 repro

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Fold 30314 subscription cancel e2e test into 14117 test

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011Jc2P2sKm39MghfKPxCSiv

* Fold 16108 tooltip e2e test into download format test

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011Jc2P2sKm39MghfKPxCSiv

* Merge public sharing e2e tests that share setup

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011Jc2P2sKm39MghfKPxCSiv

* Place merged 18352 card below the Orders card

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011Jc2P2sKm39MghfKPxCSiv

* Type a name before Escape in the 24629 subscriptions e2e test

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011Jc2P2sKm39MghfKPxCSiv

* Repair public sharing e2e tests that cannot fail

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011Jc2P2sKm39MghfKPxCSiv

* Tag sharing e2e tests that need maildev as external

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011Jc2P2sKm39MghfKPxCSiv

* Drop unused setup in public sharing e2e tests

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011Jc2P2sKm39MghfKPxCSiv

* Fix popover check after Escape in dashboard downloads e2e test

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011Jc2P2sKm39MghfKPxCSiv

* Keep the webhook channel in the 12349 alert e2e test

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011Jc2P2sKm39MghfKPxCSiv

* Tighten subscriptions e2e checks found in parity review

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011Jc2P2sKm39MghfKPxCSiv

* Tighten sharing reproductions e2e checks found in parity review

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011Jc2P2sKm39MghfKPxCSiv

* Restore admin and UI-created link coverage in public sharing e2e tests

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011Jc2P2sKm39MghfKPxCSiv

* Fix 7 sharing e2e failures from CI

* Restore 48402 coverage and merge more sharing e2e tests

- Restore the metabase#48402 delete-alert step in the 12349 test and check the delete modal
- Make 18669 check the email filter value and 21559 check the card image
- Merge public dashboard download flag tests with the real downloads
- Merge static-viz, branding, and default-filter subscription tests
- Merge dashboard PDF and PNG download tests and check download parameters
- Merge the two background=false public dashboard tests

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Address review on sharing e2e consolidation

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Fix sharing e2e CI failures

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-06 17:39:59 -04:00

247 lines
7.9 KiB
JavaScript

const { H } = cy;
import { SAMPLE_DATABASE } from "e2e/support/cypress_sample_database";
import {
ORDERS_DASHBOARD_ID,
ORDERS_MODEL_ID,
ORDERS_QUESTION_ID,
} from "e2e/support/cypress_sample_instance_data";
describe("scenarios > alert", () => {
beforeEach(() => {
H.restore();
cy.signInAsAdmin();
});
describe("with nothing set", () => {
it("should prompt admins to add email/slack credentials and tell non-admins to ask an admin", () => {
H.visitQuestion(ORDERS_QUESTION_ID);
cy.findByLabelText("Move, trash, and more…").click();
H.popover().findByText("Create an alert").click();
H.modal().within(() => {
cy.findByText(
"To get notified when something happens, or to send this chart on a schedule, first set up email, Slack, or a webhook.",
);
cy.findByText("Set up email")
.should("be.visible")
.closest("a")
.should("have.attr", "href", "/admin/settings/email");
cy.findByText("Set up Slack")
.should("be.visible")
.closest("a")
.should("have.attr", "href", "/admin/settings/slack");
cy.findByText("Add a webhook")
.should("be.visible")
.closest("a")
.should("have.attr", "href", "/admin/settings/webhooks");
});
cy.signInAsNormalUser();
H.visitQuestion(ORDERS_QUESTION_ID);
cy.findByLabelText("Move, trash, and more…").click();
H.popover().findByText("Create an alert").click();
H.modal().within(() => {
cy.findByText(
"To get notified when something happens, or to send this chart on a schedule, ask your admin to set up email, Slack, or a webhook.",
).should("be.visible");
cy.findByText("Set up email").should("not.exist");
cy.findByText("Set up Slack").should("not.exist");
cy.findByText("Add a webhook").should("not.exist");
});
});
});
describe("with a webhook", { tags: ["@external"] }, () => {
beforeEach(() => {
H.setupNotificationChannel({
name: "Foo Hook",
description: "This is a hook",
});
H.setupNotificationChannel({
name: "Bar Hook",
description: "This is another hook",
});
});
it("should be able to create and delete alerts with webhooks enabled", () => {
H.visitQuestion(ORDERS_QUESTION_ID);
cy.findByLabelText("Move, trash, and more…").click();
H.popover().findByText("Create an alert").click();
H.addNotificationHandlerChannel("Bar Hook");
H.selectScheduleTime();
cy.findByRole("button", { name: "Done" }).click();
H.notificationList().findByText("Your alert is all set up.");
cy.findByLabelText("Move, trash, and more…").click();
H.popover().findByText("Edit alerts").click();
H.modal().within(() => {
cy.findByText("Edit alerts").should("be.visible");
cy.icon("webhook").should("be.visible");
cy.findByText(/Created by you/)
.should("be.visible")
.realHover();
cy.icon("trash").click();
});
cy.findByRole("button", { name: "Delete it" }).click();
H.notificationList().findByText("The alert was successfully deleted.");
// delete modal should close
H.modal().should("not.exist");
});
});
it("should not be offered for models (metabase#37893)", () => {
H.visitModel(ORDERS_MODEL_ID);
cy.findByTestId("view-footer").within(() => {
cy.findByTestId("question-row-count")
.should("have.text", "Showing first 2,000 rows")
.and("be.visible");
cy.icon("download").should("exist");
});
H.sharingMenuButton().should("not.exist");
cy.findByLabelText("Move, trash, and more…").click();
H.popover().within(() => {
cy.findByText("Turn back to saved question").should("be.visible");
cy.findByText("Create an alert").should("not.exist");
cy.findByText("Edit alerts").should("not.exist");
});
});
it("can set up an alert for a question saved in a dashboard", () => {
H.setupSMTP();
H.createQuestion(
{
name: "Total Orders",
database_id: SAMPLE_DATABASE.id,
dashboard_id: ORDERS_DASHBOARD_ID,
query: {
"source-table": SAMPLE_DATABASE.ORDERS_ID,
aggregation: [["count"]],
},
display: "scalar",
},
{ visitQuestion: true },
);
cy.findByLabelText("Move, duplicate, and more…").click();
H.popover().findByText("Create an alert").click();
H.selectScheduleTime();
H.modal().button("Done").click();
cy.findByLabelText("Move, duplicate, and more…").click();
H.popover().findByText("Edit alerts").click();
H.modal().within(() => {
cy.findByText("Edit alerts").should("be.visible");
cy.findByText(/Created by you/).should("be.visible");
});
});
describe("approved domains (EE)", { tags: "@external" }, () => {
const allowedDomain = "metabase.test";
const deniedDomain = "metabase.example";
const deniedEmail = `mailer@${deniedDomain}`;
// We're not exposing allowed domains to normal users.
const normalUserAlertError = `Failed save alert. The following email addresses are not allowed: ${deniedEmail}`;
const normalUserSubscriptionError = `Cannot create subscription. The following email addresses are not allowed: ${deniedEmail} Please contact your administrator.`;
const adminAlertError = `You're only allowed to email alerts to addresses ending in ${allowedDomain}`;
const adminSubscriptionError = `You're only allowed to email subscriptions to addresses ending in ${allowedDomain}`;
function addEmailRecipient(email) {
cy.findByTestId("token-field")
.findByRole("combobox")
.click()
.type(`${email}`)
.blur();
}
function setAllowedDomains() {
H.updateSetting("subscription-allowed-domains", allowedDomain);
}
beforeEach(() => {
H.activateToken("pro-self-hosted");
H.setupSMTP();
setAllowedDomains();
});
it("should validate approved email domains for a question alert and a dashboard subscription (metabase#17977)", () => {
H.visitQuestion(ORDERS_QUESTION_ID);
cy.findByLabelText("Move, trash, and more…").click();
H.popover().findByText("Create an alert").click();
H.selectScheduleTime();
H.modal().within(() => {
cy.findByText("New alert").should("be.visible");
addEmailRecipient(deniedEmail);
cy.findByText(adminAlertError);
cy.button("Done").should("be.disabled");
});
H.visitDashboard(ORDERS_DASHBOARD_ID);
H.toggleDashboardSubscriptionsSidebar();
H.sidebar().within(() => {
cy.findByText("Email it").click();
addEmailRecipient(deniedEmail);
// Reproduces metabase#17977
cy.button("Send email now").should("be.disabled");
cy.button("Done").should("be.disabled");
cy.findByText(adminSubscriptionError);
});
});
it("should not display the list of approved domains for non-admins (metabase#57138)", () => {
cy.signInAsNormalUser();
H.visitQuestion(ORDERS_QUESTION_ID);
cy.findByLabelText("Move, trash, and more…").click();
H.popover().findByText("Create an alert").click();
H.selectScheduleTime();
H.modal().within(() => {
cy.findByText("New alert").should("be.visible");
addEmailRecipient(deniedEmail);
cy.button("Done").click();
});
cy.findByTestId("toast-undo").within(() => {
cy.root().should("have.text", normalUserAlertError);
});
H.visitDashboard(ORDERS_DASHBOARD_ID);
H.toggleDashboardSubscriptionsSidebar();
H.sidebar().within(() => {
addEmailRecipient(deniedEmail);
cy.button("Done").click();
});
cy.findByTestId("toast-undo").within(() => {
cy.root().should("have.text", normalUserSubscriptionError);
});
});
});
});