mirror of
https://github.com/openobserve/openobserve.git
synced 2026-11-05 16:10:43 +00:00
test: add regression tests for same-dashboard drilldown URL clobbering (#11653)
This commit is contained in:
@@ -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(() => {});
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user