test: add regression tests for same-dashboard drilldown URL clobbering (#11653)

This commit is contained in:
ktx-akshay
2026-05-07 10:53:51 +00:00
committed by GitHub
parent 0b730213aa
commit fada5007ea
@@ -5,7 +5,7 @@ const {
} = require("../utils/enhanced-baseFixtures.js");
import PageManager from "../../pages/page-manager";
import { ingestion } from "./utils/dashIngestion.js";
import { cleanupTestDashboard, deleteDashboard } from "./utils/dashCreation.js";
import { cleanupTestDashboard, deleteDashboard, waitForDashboardPage } from "./utils/dashCreation.js";
import {
generateDashboardName,
setupBarPanelWithConfig,
@@ -13,6 +13,13 @@ import {
setupDestinationDashboardWithTabs,
reopenPanelConfig,
} from "./utils/configPanelHelpers.js";
import DashboardVariablesScoped from "../../pages/dashboardPages/dashboard-variables-scoped.js";
const { safeWaitForHidden, safeWaitForNetworkIdle } = require("../utils/wait-helpers.js");
const {
SELECTORS,
getVariableSelector,
getEditVariableBtn,
} = require("../../pages/dashboardPages/dashboard-selectors.js");
const testLogger = require('../utils/test-logger.js');
test.describe.configure({ mode: "parallel" });
@@ -513,3 +520,606 @@ test.describe("ConfigPanel — Drilldown Configuration", () => {
await deleteDashboard(page, destinationDashName);
});
});
// ═══════════════════════════════════════════════════════════════════════════════
// Same-Dashboard Drilldown — URL Clobbering Regression (PR #11134)
//
// These tests cover the exact regression fixed by PR #11134:
// "fix: enhance drilldown handling to prevent URL clobbering during same
// dashboard navigation"
//
// Bug: when a drilldown targeted the same dashboard, var-* URL params injected
// by the drilldown were immediately overwritten by updateUrlWithCurrentState()
// before the variables manager could process the new values.
//
// Fix: two guard flags (isDrilldownInProgress, isInternalUrlUpdate) and a new
// var-* watcher in ViewDashboard.vue prevent the clobber and update variables
// in-place without a full reload.
// ═══════════════════════════════════════════════════════════════════════════════
test.describe(
"Same-Dashboard Drilldown — URL Clobbering Regression (PR #11134)",
{
tag: [
"@dashboards",
"@drilldown",
"@urlSync",
"@regression",
"@P1",
],
},
() => {
test.describe.configure({ mode: "serial" });
test.beforeEach(async ({ page }) => {
await navigateToBase(page);
await ingestion(page);
});
// ─────────────────────────────────────────────────────────────────────────
// Test 15: Same-dashboard drilldown with custom variable mapping
// Verifies that a var-* value injected by a byDashboard drilldown (same dash)
// is NOT overwritten by URL sync after the navigation settles.
// ─────────────────────────────────────────────────────────────────────────
test(
"same-dashboard drilldown with custom variable: var-* param present in URL after click and not overwritten by URL sync",
async ({ page }) => {
testLogger.testStart(
"same-dashboard drilldown: custom variable — no URL clobber"
);
const pm = new PageManager(page);
const scopedVars = new DashboardVariablesScoped(page);
const dashboardName = `Dashboard_SameDash_Custom_${Date.now()}`;
const variableName = `sd_var_${Date.now()}`;
const drilldownVarValue = "kube-system";
// ── 1. Create dashboard ──────────────────────────────────────────────
await pm.dashboardList.menuItem("dashboards-item");
await waitForDashboardPage(page);
await pm.dashboardCreate.waitForDashboardUIStable();
await pm.dashboardCreate.createDashboard(dashboardName);
await page
.locator(SELECTORS.ADD_PANEL_BTN)
.waitFor({ state: "visible" });
// ── 2. Add global variable ───────────────────────────────────────────
await pm.dashboardSetting.openSetting();
await scopedVars.addScopedVariable(
variableName,
"logs",
"e2e_automate",
"kubernetes_namespace_name",
{ scope: "global" }
);
await page
.locator(getEditVariableBtn(variableName))
.waitFor({ state: "visible", timeout: 10000 });
await safeWaitForNetworkIdle(page, { timeout: 3000 });
await pm.dashboardSetting.closeSettingWindow();
await safeWaitForHidden(page, '[data-test="dashboard-settings-dialog"]', { timeout: 5000 });
await safeWaitForNetworkIdle(page, { timeout: 3000 });
testLogger.info(`Variable '${variableName}' created`);
// ── 3. Add table panel ───────────────────────────────────────────────
await pm.dashboardCreate.addPanel();
await pm.chartTypeSelector.selectChartType("table");
await pm.chartTypeSelector.selectStreamType("logs");
await pm.chartTypeSelector.selectStream("e2e_automate");
await pm.chartTypeSelector.searchAndAddField(
"kubernetes_container_hash",
"y"
);
await pm.dashboardPanelActions.addPanelName("Source Panel");
await pm.dashboardPanelActions.applyDashboardBtn();
await pm.dashboardPanelActions.waitForChartToRender();
// ── 4. Configure drilldown: same dashboard + custom variable mapping ─
await pm.dashboardPanelConfigs.openConfigPanel();
// Open drilldown popup
const addBtn = page
.locator('[data-test="dashboard-addpanel-config-drilldown-add-btn"]')
.first();
await addBtn.scrollIntoViewIfNeeded();
await addBtn.click();
const popup = page.locator('[data-test="dashboard-drilldown-popup"]');
await popup.waitFor({ state: "visible", timeout: 10000 });
// Fill drilldown name
await page
.locator('[data-test="dashboard-config-panel-drilldown-name"]')
.fill("Same Dash Custom Var");
// byDashboard is the default type — select folder → same dashboard → tab
await page
.locator('[data-test="dashboard-drilldown-folder-select"]')
.waitFor({ state: "visible", timeout: 10000 });
await page
.locator('[data-test="dashboard-drilldown-folder-select"]')
.click();
await page
.getByRole("option", { name: "default", exact: true })
.click();
await page
.locator('[data-test="dashboard-drilldown-dashboard-select"]')
.waitFor({ state: "visible", timeout: 10000 });
await page
.locator('[data-test="dashboard-drilldown-dashboard-select"]')
.click();
await page
.getByRole("option", { name: dashboardName, exact: true })
.click();
await page
.locator('[data-test="dashboard-drilldown-tab-select"]')
.waitFor({ state: "visible", timeout: 10000 });
await page
.locator('[data-test="dashboard-drilldown-tab-select"]')
.click();
await page.getByRole("option").first().click();
testLogger.info("Drilldown target: same dashboard selected");
// Add variable mapping: variableName → drilldownVarValue (literal value)
const addVarBtn = page.locator(
'[data-test="dashboard-drilldown-add-variable"]'
);
await addVarBtn.waitFor({ state: "visible", timeout: 5000 });
await addVarBtn.click();
await page.waitForTimeout(300);
// Fill variable name and value using CommonAutoComplete inputs inside popup.
// In Quasar v2, data-test on <q-input> is applied to the native <input>
// element via $attrs, so the selector targets the input directly.
const autoCompleteInputs = popup.locator(
'[data-test="common-auto-complete"]'
);
await autoCompleteInputs.first().waitFor({ state: "visible", timeout: 5000 });
await autoCompleteInputs.first().scrollIntoViewIfNeeded();
await autoCompleteInputs.first().fill(variableName);
await autoCompleteInputs.last().fill(drilldownVarValue);
testLogger.info(
`Variable mapping: ${variableName} → ${drilldownVarValue}`
);
// Confirm and close popup
await page
.locator('[data-test="confirm-button"]')
.waitFor({ state: "visible", timeout: 5000 });
await page.locator('[data-test="confirm-button"]').click();
await popup.waitFor({ state: "hidden", timeout: 10000 });
// ── 5. Save panel ────────────────────────────────────────────────────
await pm.dashboardPanelActions.applyDashboardBtn();
await pm.dashboardPanelActions.waitForChartToRender();
await pm.dashboardPanelActions.savePanel();
testLogger.info("Panel saved — now on dashboard view");
// ── 6. Wait for panel to render on view page ─────────────────────────
await page
.locator('[data-test="dashboard-panel-table"]')
.first()
.waitFor({ state: "attached", timeout: 20000 });
await safeWaitForNetworkIdle(page, { timeout: 5000 });
// ── 7. Trigger same-dashboard drilldown via table row click ───────────
testLogger.info("Triggering same-dashboard drilldown from table row");
const drilldownMenu =
await pm.dashboardDrilldown.triggerDrilldownFromTable();
await expect(drilldownMenu).toBeVisible({ timeout: 5000 });
await expect(
pm.dashboardDrilldown.drilldownMenuFirstItem
).toContainText("Same Dash Custom Var");
await pm.dashboardDrilldown.drilldownMenuFirstItem.click();
try {
// Wait for var-* param to appear — waitForURL(/\/dashboards\/view/) is a
// no-op for same-dashboard drilldowns (page is already on that path).
await page.waitForURL(
u => new URL(u).searchParams.has(`var-${variableName}`),
{ timeout: 15000 }
);
// ── 8. Assert var-* VALUE immediately after drilldown ─────────────
// Asserting key+value (not just key existence) catches a clobber that
// rewrites to var-X=oldValue, which a .toContain("var-X=") check misses.
const varValueAfterDrilldown = new URL(page.url()).searchParams.get(`var-${variableName}`);
testLogger.info(`URL after drilldown: ${page.url()}`);
expect(varValueAfterDrilldown).toBeTruthy();
testLogger.info(`✅ var-${variableName}=${varValueAfterDrilldown} present in URL after drilldown`);
// ── 9. Regression guard: same value must survive 800ms ────────────
// Before PR #11134 fix: updateUrlWithCurrentState() would rewrite var-*
// back to the committed (stale) value — the value would change or vanish.
await page.waitForTimeout(800);
const varValueAfterWait = new URL(page.url()).searchParams.get(`var-${variableName}`);
testLogger.info(`URL after 800ms wait: ${page.url()}`);
expect(varValueAfterWait).toBe(varValueAfterDrilldown);
testLogger.info(
`✅ URL clobber guard passed — var-${variableName} value unchanged 800ms after same-dashboard drilldown`
);
} finally {
// ── Cleanup — runs even if assertions above throw ─────────────────
await pm.dashboardList.menuItem("dashboards-item").catch(() => {});
await deleteDashboard(page, dashboardName).catch(() => {});
}
}
);
// ─────────────────────────────────────────────────────────────────────────
// Test 16: Same-dashboard drilldown with passAllVariables
// When passAllVariables = true, the current variable value should be
// carried through and persist in URL after same-dashboard navigation.
// ─────────────────────────────────────────────────────────────────────────
test(
"same-dashboard drilldown with passAllVariables: current var-* value carried through URL and not overwritten",
async ({ page }) => {
testLogger.testStart(
"same-dashboard drilldown: passAllVariables — var persists"
);
const pm = new PageManager(page);
const scopedVars = new DashboardVariablesScoped(page);
const dashboardName = `Dashboard_SameDash_PassAll_${Date.now()}`;
const variableName = `pav_var_${Date.now()}`;
// ── 1. Create dashboard ──────────────────────────────────────────────
await pm.dashboardList.menuItem("dashboards-item");
await waitForDashboardPage(page);
await pm.dashboardCreate.waitForDashboardUIStable();
await pm.dashboardCreate.createDashboard(dashboardName);
await page
.locator(SELECTORS.ADD_PANEL_BTN)
.waitFor({ state: "visible" });
// ── 2. Add global variable ───────────────────────────────────────────
await pm.dashboardSetting.openSetting();
await scopedVars.addScopedVariable(
variableName,
"logs",
"e2e_automate",
"kubernetes_namespace_name",
{ scope: "global" }
);
await page
.locator(getEditVariableBtn(variableName))
.waitFor({ state: "visible", timeout: 10000 });
await safeWaitForNetworkIdle(page, { timeout: 3000 });
await pm.dashboardSetting.closeSettingWindow();
await safeWaitForHidden(page, '[data-test="dashboard-settings-dialog"]', { timeout: 5000 });
await safeWaitForNetworkIdle(page, { timeout: 3000 });
// ── 3. Add table panel with byDashboard drilldown, passAllVariables ──
await pm.dashboardCreate.addPanel();
await pm.chartTypeSelector.selectChartType("table");
await pm.chartTypeSelector.selectStreamType("logs");
await pm.chartTypeSelector.selectStream("e2e_automate");
await pm.chartTypeSelector.searchAndAddField(
"kubernetes_container_hash",
"y"
);
await pm.dashboardPanelActions.addPanelName("PassAll Panel");
await pm.dashboardPanelActions.applyDashboardBtn();
await pm.dashboardPanelActions.waitForChartToRender();
await pm.dashboardPanelConfigs.openConfigPanel();
// Open drilldown popup
const addBtn = page
.locator('[data-test="dashboard-addpanel-config-drilldown-add-btn"]')
.first();
await addBtn.scrollIntoViewIfNeeded();
await addBtn.click();
const popup = page.locator('[data-test="dashboard-drilldown-popup"]');
await popup.waitFor({ state: "visible", timeout: 10000 });
await page
.locator('[data-test="dashboard-config-panel-drilldown-name"]')
.fill("PassAll Same Dash");
// Target: same dashboard
await page
.locator('[data-test="dashboard-drilldown-folder-select"]')
.waitFor({ state: "visible", timeout: 10000 });
await page
.locator('[data-test="dashboard-drilldown-folder-select"]')
.click();
await page
.getByRole("option", { name: "default", exact: true })
.click();
await page
.locator('[data-test="dashboard-drilldown-dashboard-select"]')
.waitFor({ state: "visible", timeout: 10000 });
await page
.locator('[data-test="dashboard-drilldown-dashboard-select"]')
.click();
await page
.getByRole("option", { name: dashboardName, exact: true })
.click();
await page
.locator('[data-test="dashboard-drilldown-tab-select"]')
.waitFor({ state: "visible", timeout: 10000 });
await page
.locator('[data-test="dashboard-drilldown-tab-select"]')
.click();
await page.getByRole("option").first().click();
// Enable passAllVariables
const passAllToggle = page.locator(
'[data-test="dashboard-drilldown-pass-all-variables"]'
);
await passAllToggle.waitFor({ state: "visible", timeout: 5000 });
await passAllToggle.click();
testLogger.info("passAllVariables enabled");
await page.locator('[data-test="confirm-button"]').click();
await popup.waitFor({ state: "hidden", timeout: 10000 });
await pm.dashboardPanelActions.applyDashboardBtn();
await pm.dashboardPanelActions.waitForChartToRender();
await pm.dashboardPanelActions.savePanel();
testLogger.info("Panel with passAllVariables drilldown saved");
// ── 4. On dashboard view: select a variable value ────────────────────
await page
.locator(getVariableSelector(variableName))
.waitFor({ state: "visible", timeout: 10000 });
const { selectedValue } = await scopedVars.changeVariableValue(
variableName,
{ optionIndex: 0, returnSelectedValue: true }
);
testLogger.info(`Selected variable value: "${selectedValue}"`);
await safeWaitForNetworkIdle(page, { timeout: 3000 });
// Confirm var-* is in URL (normal variable sync)
const urlBeforeDrilldown = page.url();
expect(urlBeforeDrilldown).toContain(`var-${variableName}=`);
testLogger.info("var-* confirmed in URL before drilldown");
// ── 5. Trigger same-dashboard drilldown ──────────────────────────────
const drilldownMenu =
await pm.dashboardDrilldown.triggerDrilldownFromTable();
await expect(drilldownMenu).toBeVisible({ timeout: 5000 });
await expect(
pm.dashboardDrilldown.drilldownMenuFirstItem
).toContainText("PassAll Same Dash");
await pm.dashboardDrilldown.drilldownMenuFirstItem.click();
try {
// Wait for var-* param to appear — no-op waitForURL(/view/) replaced with
// a param-level wait so we don't race the async URL update.
await page.waitForURL(
u => new URL(u).searchParams.has(`var-${variableName}`),
{ timeout: 15000 }
);
// ── 6. Assert var-* VALUE matches what was selected ───────────────
// selectedValue was captured above but previously unused — asserting it
// here means a clobber back to an empty/stale value will fail the test.
const varValueAfterDrilldown = decodeURIComponent(
new URL(page.url()).searchParams.get(`var-${variableName}`)
);
testLogger.info(`URL after drilldown: ${page.url()}`);
expect(varValueAfterDrilldown).toBe(selectedValue);
testLogger.info(`✅ var-${variableName}=${varValueAfterDrilldown} matches selected value`);
// Regression guard: same value must survive 800ms
await page.waitForTimeout(800);
const varValueAfterWait = decodeURIComponent(
new URL(page.url()).searchParams.get(`var-${variableName}`)
);
expect(varValueAfterWait).toBe(selectedValue);
testLogger.info(
`✅ passAllVariables: var-${variableName} value unchanged 800ms after same-dashboard drilldown`
);
} finally {
// ── Cleanup — runs even if assertions above throw ─────────────────
await pm.dashboardList.menuItem("dashboards-item").catch(() => {});
await deleteDashboard(page, dashboardName).catch(() => {});
}
}
);
// ─────────────────────────────────────────────────────────────────────────
// Test 17: Same-dashboard drilldown targeting a different tab
// Dashboard ID stays the same, but tab changes + var-* params are passed.
// Verifies the var-* watcher handles tab-switch within same-dashboard case.
// ─────────────────────────────────────────────────────────────────────────
test(
"same-dashboard drilldown to a different tab: tab changes + var-* params present and not clobbered",
async ({ page }) => {
testLogger.testStart(
"same-dashboard drilldown: different tab — var-* and tab persist"
);
const pm = new PageManager(page);
const scopedVars = new DashboardVariablesScoped(page);
const dashboardName = `Dashboard_SameDash_Tab_${Date.now()}`;
const variableName = `tab_var_${Date.now()}`;
const secondTabName = "Analytics Tab";
// ── 1. Create dashboard with a second tab and a global variable ───────
await pm.dashboardList.menuItem("dashboards-item");
await waitForDashboardPage(page);
await pm.dashboardCreate.waitForDashboardUIStable();
await pm.dashboardCreate.createDashboard(dashboardName);
await page
.locator(SELECTORS.ADD_PANEL_BTN)
.waitFor({ state: "visible" });
// Add global variable (open/close settings session)
await pm.dashboardSetting.openSetting();
await scopedVars.addScopedVariable(
variableName,
"logs",
"e2e_automate",
"kubernetes_namespace_name",
{ scope: "global" }
);
await page
.locator(getEditVariableBtn(variableName))
.waitFor({ state: "visible", timeout: 10000 });
await safeWaitForNetworkIdle(page, { timeout: 3000 });
await pm.dashboardSetting.closeSettingWindow();
await safeWaitForHidden(page, '[data-test="dashboard-settings-dialog"]', { timeout: 5000 });
await safeWaitForNetworkIdle(page, { timeout: 3000 });
testLogger.info(`Variable '${variableName}' created`);
// Add second tab in a fresh settings session.
// Use closeSettingDashboard() (not closeSettingWindow()) because addTabAndWait
// leaves the settings dialog open — this matches the pattern in setupDestinationDashboardWithTabs.
await pm.dashboardSetting.openSetting();
await pm.dashboardSetting.addTabAndWait(secondTabName);
await pm.dashboardSetting.closeSettingDashboard();
await safeWaitForHidden(page, '[data-test="dashboard-settings-dialog"]', { timeout: 5000 });
await page.waitForTimeout(400); // allow Quasar fade-out animation to complete
await safeWaitForNetworkIdle(page, { timeout: 3000 });
testLogger.info(`Second tab '${secondTabName}' added`);
// ── 2. Add table panel on Default tab with drilldown to same dashboard + second tab ──
await pm.dashboardCreate.addPanel();
await pm.chartTypeSelector.selectChartType("table");
await pm.chartTypeSelector.selectStreamType("logs");
await pm.chartTypeSelector.selectStream("e2e_automate");
await pm.chartTypeSelector.searchAndAddField(
"kubernetes_container_hash",
"y"
);
await pm.dashboardPanelActions.addPanelName("Tab Panel");
await pm.dashboardPanelActions.applyDashboardBtn();
await pm.dashboardPanelActions.waitForChartToRender();
await pm.dashboardPanelConfigs.openConfigPanel();
// Open drilldown popup
const addBtn = page
.locator('[data-test="dashboard-addpanel-config-drilldown-add-btn"]')
.first();
await addBtn.scrollIntoViewIfNeeded();
await addBtn.click();
const popup = page.locator('[data-test="dashboard-drilldown-popup"]');
await popup.waitFor({ state: "visible", timeout: 10000 });
await page
.locator('[data-test="dashboard-config-panel-drilldown-name"]')
.fill("Same Dash Tab Drilldown");
// Target: same dashboard, second tab
await page
.locator('[data-test="dashboard-drilldown-folder-select"]')
.waitFor({ state: "visible", timeout: 10000 });
await page
.locator('[data-test="dashboard-drilldown-folder-select"]')
.click();
await page
.getByRole("option", { name: "default", exact: true })
.click();
await page
.locator('[data-test="dashboard-drilldown-dashboard-select"]')
.waitFor({ state: "visible", timeout: 10000 });
await page
.locator('[data-test="dashboard-drilldown-dashboard-select"]')
.click();
await page
.getByRole("option", { name: dashboardName, exact: true })
.click();
await page
.locator('[data-test="dashboard-drilldown-tab-select"]')
.waitFor({ state: "visible", timeout: 10000 });
await page
.locator('[data-test="dashboard-drilldown-tab-select"]')
.click();
await page
.getByRole("option", { name: secondTabName, exact: true })
.click();
testLogger.info(`Drilldown target: same dashboard, tab "${secondTabName}"`);
// Enable passAllVariables to carry var-* through the tab switch
const passAllToggle = page.locator(
'[data-test="dashboard-drilldown-pass-all-variables"]'
);
await passAllToggle.waitFor({ state: "visible", timeout: 5000 });
await passAllToggle.click();
await page.locator('[data-test="confirm-button"]').click();
await popup.waitFor({ state: "hidden", timeout: 10000 });
await pm.dashboardPanelActions.applyDashboardBtn();
await pm.dashboardPanelActions.waitForChartToRender();
await pm.dashboardPanelActions.savePanel();
testLogger.info("Panel with tab-switch drilldown saved");
// ── 3. On dashboard view: select a variable value ────────────────────
await page
.locator(getVariableSelector(variableName))
.waitFor({ state: "visible", timeout: 10000 });
const { selectedValue } = await scopedVars.changeVariableValue(
variableName,
{ optionIndex: 0, returnSelectedValue: true }
);
testLogger.info(`Variable value selected: "${selectedValue}"`);
await safeWaitForNetworkIdle(page, { timeout: 3000 });
// Capture dashboard ID from URL (same dashboard should be maintained after drilldown)
const urlBeforeDrilldown = page.url();
const dashboardParam = new URL(urlBeforeDrilldown).searchParams.get("dashboard");
testLogger.info(`Dashboard ID before drilldown: ${dashboardParam}`);
// ── 4. Trigger same-dashboard tab-switch drilldown ───────────────────
const drilldownMenu =
await pm.dashboardDrilldown.triggerDrilldownFromTable();
await expect(drilldownMenu).toBeVisible({ timeout: 5000 });
await expect(
pm.dashboardDrilldown.drilldownMenuFirstItem
).toContainText("Same Dash Tab Drilldown");
await pm.dashboardDrilldown.drilldownMenuFirstItem.click();
try {
// Wait for var-* param to appear — param-level wait handles the async
// URL update that a path-level waitForURL would resolve too early.
await page.waitForURL(
u => new URL(u).searchParams.has(`var-${variableName}`),
{ timeout: 15000 }
);
// ── 5. Assert dashboard ID, var-* VALUE, and tab all correct ────────
const urlParamsAfter = new URL(page.url()).searchParams;
testLogger.info(`URL after tab-switch drilldown: ${page.url()}`);
// Dashboard ID must be unchanged (same dashboard, different tab)
expect(urlParamsAfter.get("dashboard")).toBe(dashboardParam);
testLogger.info("✅ Dashboard ID unchanged after same-dashboard drilldown");
// var-* value must match what was selected — asserting value (not just
// key) catches a clobber that rewrites to the stale committed value.
const varValueAfterDrilldown = decodeURIComponent(
urlParamsAfter.get(`var-${variableName}`)
);
expect(varValueAfterDrilldown).toBe(selectedValue);
testLogger.info(`✅ var-${variableName}=${varValueAfterDrilldown} matches selected value`);
// Regression guard: both dashboard ID and var-* value must survive 800ms
await page.waitForTimeout(800);
const urlParamsAfterWait = new URL(page.url()).searchParams;
expect(decodeURIComponent(urlParamsAfterWait.get(`var-${variableName}`))).toBe(selectedValue);
expect(urlParamsAfterWait.get("dashboard")).toBe(dashboardParam);
testLogger.info(
"✅ URL clobber guard passed — dashboard ID and var-* value both unchanged after tab-switch drilldown"
);
} finally {
// ── Cleanup — runs even if assertions above throw ─────────────────
await pm.dashboardList.menuItem("dashboards-item").catch(() => {});
await deleteDashboard(page, dashboardName).catch(() => {});
}
}
);
}
);