Files
metabase/e2e/test/scenarios/sharing/public-dashboard.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

343 lines
8.7 KiB
JavaScript

const { H } = cy;
import { SAMPLE_DATABASE } from "e2e/support/cypress_sample_database";
const { PRODUCTS, ORDERS_ID } = SAMPLE_DATABASE;
const questionDetails = {
name: "sql param",
native: {
query: "select count(*) from products where {{c}}",
"template-tags": {
c: {
id: "e126f242-fbaa-1feb-7331-21ac59f021cc",
name: "c",
"display-name": "Category",
type: "dimension",
dimension: ["field", PRODUCTS.CATEGORY, null],
default: null,
"widget-type": "category",
},
},
},
display: "scalar",
};
const textFilter = {
id: "1",
type: "string/=",
name: "Text",
slug: "text",
sectionId: "string",
};
const unusedFilter = {
id: "2",
type: "number/=",
name: "Number",
slug: "number",
sectionId: "number",
};
const tab1 = {
id: 1,
name: "Tab 1",
};
const tab2 = {
id: 2,
name: "Tab 2",
};
const dashboardDetails = {
parameters: [textFilter, unusedFilter],
tabs: [tab1, tab2],
};
const COUNT_ALL = "200";
const COUNT_DOOHICKEY = "42";
const USERS = {
"admin user": () => cy.signInAsAdmin(),
"user with no permissions": () => cy.signIn("none"),
"anonymous user": () => cy.signOut(),
};
const prepareDashboard = () => {
H.createNativeQuestionAndDashboard({
questionDetails,
dashboardDetails,
}).then(
({
body: { id, card_id, dashboard_id, dashboard_tab_id },
dashboardTabs,
}) => {
cy.wrap(dashboard_id).as("dashboardId");
// Connect filter to the card
cy.request("PUT", `/api/dashboard/${dashboard_id}`, {
tabs: dashboardTabs,
dashcards: [
{
id,
dashboard_tab_id,
card_id,
row: 0,
col: 0,
size_x: 8,
size_y: 6,
parameter_mappings: [
{
parameter_id: textFilter.id,
card_id,
target: ["dimension", ["template-tag", "c"]],
},
],
},
],
});
},
);
};
describe("scenarios > public > dashboard", () => {
beforeEach(() => {
H.restore();
cy.signInAsAdmin();
prepareDashboard();
});
Object.entries(USERS).map(([userType, setUser]) =>
describe(`${userType}`, () => {
it("should be able to view public dashboards", () => {
cy.get("@dashboardId").then((id) => {
cy.request("POST", `/api/dashboard/${id}/public_link`).then(
({ body: { uuid } }) => {
setUser();
cy.visit(`/public/dashboard/${uuid}`);
},
);
});
cy.findByTestId("scalar-value").should("have.text", COUNT_ALL);
H.filterWidget().click();
H.popover().within(() => {
cy.findByText("Doohickey").click();
cy.button("Add filter").click();
});
cy.findByTestId("scalar-value").should("have.text", COUNT_DOOHICKEY);
});
}),
);
it("should respect 'disable auto-apply filters' in a public dashboard", () => {
cy.get("@dashboardId").then((id) => {
cy.request("PUT", `/api/dashboard/${id}`, {
auto_apply_filters: false,
});
H.visitPublicDashboard(id);
});
cy.findByTestId("scalar-value").should("have.text", COUNT_ALL);
H.applyFilterToast().should("not.exist");
H.filterWidget().click();
H.popover().within(() => {
cy.findByText("Doohickey").click();
cy.button("Add filter").click();
});
H.applyFilterButton().should("be.visible");
cy.findByTestId("scalar-value").should("have.text", COUNT_ALL);
H.applyFilterButton().click();
H.applyFilterToast().should("not.exist");
cy.findByTestId("scalar-value").should("have.text", COUNT_DOOHICKEY);
});
it("should only display filters mapped to cards on the selected tab", () => {
cy.get("@dashboardId").then((id) => {
H.visitPublicDashboard(id);
});
// new dashboards should default to 'fixed' width
H.assertDashboardFixedWidth();
H.dashboardParametersContainer().within(() => {
cy.findByText(textFilter.name).should("be.visible");
cy.findByText(unusedFilter.name).should("not.exist");
});
H.goToTab(tab2.name);
H.dashboardParametersContainer().should("not.exist");
cy.findByTestId("embed-frame").within(() => {
cy.findByText(textFilter.name).should("not.exist");
cy.findByText(unusedFilter.name).should("not.exist");
});
});
it("should render when a filter passed with value starting from '0', support #theme=dark, and respect full width (metabase#41483, metabase#65731)", () => {
cy.get("@dashboardId").then((id) => {
cy.request("PUT", `/api/dashboard/${id}`, {
width: "full",
});
H.visitPublicDashboard(id, {
params: { text: "002" },
hash: {
theme: "dark",
},
});
});
cy.url().should("include", "text=002");
H.filterWidget().findByText("002").should("be.visible");
cy.log("dark theme should have white text");
cy.findByRole("heading", {
name: "Test Dashboard",
}).should("have.css", "color", "rgba(255, 255, 255, 0.95)");
H.assertDashboardFullWidth();
});
it("should respect click behavior", () => {
H.createDashboardWithQuestions({
dashboardName: "test click behavior",
questions: [
{
name: "orders",
query: {
"source-table": ORDERS_ID,
limit: 5,
},
},
],
cards: [
{
visualization_settings: {
column_settings: {
'["name","TOTAL"]': {
click_behavior: {
type: "link",
linkType: "url",
linkTemplate: "https://metabase.com",
},
},
},
},
},
],
}).then(({ dashboard }) => {
H.visitPublicDashboard(dashboard.id);
});
// This is a hacky way to intercept the link click we create an a element
// with href on fly and remove it afterwards in lib/dom.js
cy.window().then((win) => {
cy.spy(win.document.body, "appendChild").as("appendChild");
});
H.getDashboardCard().findByText("39.72").click();
cy.get("@appendChild").then((appendChild) => {
// last call is a link
const element = appendChild.lastCall.args[0];
expect(element.tagName).to.eq("A");
expect(element.href).to.eq("https://metabase.com/");
});
});
});
describe("scenarios [EE] > public > dashboard", () => {
beforeEach(() => {
H.restore();
cy.signInAsAdmin();
prepareDashboard();
H.activateToken("pro-self-hosted");
});
it("should set the window title to `{dashboard name} · {application name}`, keep the background via `#background=false` without an iframe, and disable it inside an iframe (metabase#62391)", () => {
H.updateSetting("application-name", "Custom Application Name");
cy.get("@dashboardId").then((id) => {
H.visitPublicDashboard(id, {
hash: { background: "false" },
});
cy.title().should("eq", "Test Dashboard · Custom Application Name");
});
cy.findByTestId("embed-frame").should("exist");
cy.get("body.mb-wrapper").should(
"not.have.css",
"background-color",
"rgba(0, 0, 0, 0)",
);
cy.signInAsAdmin();
cy.visit("/");
cy.get("@dashboardId").then((id) => {
H.visitPublicDashboard(id, {
hash: { background: "false" },
onBeforeLoad: (window) => {
window.overrideIsWithinIframe = true;
},
});
});
cy.findByTestId("embed-frame").should("exist");
cy.get("body.mb-wrapper").should(
"have.css",
"background-color",
"rgba(0, 0, 0, 0)",
);
cy.window().then((win) => {
delete win.overrideIsWithinIframe;
});
});
it("should allow to set locale from the `#locale` hash parameter (metabase#50182)", () => {
// We don't have a de-CH.json file, so it should fallback to de.json, see metabase#51039 for more details
cy.intercept("GET", "**/locale-de-json*.js").as("deLocale");
cy.get("@dashboardId").then((id) => {
H.visitPublicDashboard(id, {
hash: { locale: "de-CH" },
});
});
cy.wait("@deLocale");
cy.findByRole("button", {
name: "Automatische Aktualisierung",
}).should("exist");
cy.url().should("include", "locale=de");
});
it("should handle /api/session/properties incorrect response (metabase#62501)", () => {
cy.intercept("/api/session/properties", {
statusCode: 200,
headers: {},
body: "<html><body><h1>Those aren't the droids you're looking for</h1></body></html>",
});
cy.get("@dashboardId").then(H.visitPublicDashboard);
cy.findByTestId("embed-frame").within(() => {
cy.findByText("Test Dashboard").should("exist");
});
});
});