mirror of
https://github.com/openobserve/openobserve.git
synced 2026-11-04 16:09:05 +00:00
Co-authored-by: Shrinath-O2 <214651540+Shrinath-O2@users.noreply.github.com> Co-authored-by: ktx-akshay <akshay.patil@kiara.tech>
This commit is contained in:
co-authored by
Shrinath-O2
ktx-akshay
parent
80c0719670
commit
48732e69f0
@@ -74,7 +74,8 @@
|
||||
"dashboard-sankey.spec.js",
|
||||
"dashboard-pie-donut.spec.js",
|
||||
"dashboard-metric-camelcase.spec.js",
|
||||
"dashboard-create-alert.spec.js"
|
||||
"dashboard-create-alert.spec.js",
|
||||
"dashboard-zoom-brush.spec.js"
|
||||
],
|
||||
"disabled": []
|
||||
},
|
||||
|
||||
@@ -0,0 +1,290 @@
|
||||
// Dashboard zoom-brush & panel-drag interactions.
|
||||
//
|
||||
// The ECharts dataZoom brush is canvas-drawn (not a DOM node), so a brush-to-zoom
|
||||
// is a page.mouse drag on the chart canvas, and a panel reposition is a page.mouse
|
||||
// drag on the panel header bar (GridStack only cancels the `.drag-cancel` body).
|
||||
|
||||
// The dashboard chart lives inside a GridStack tile, where a press competes with
|
||||
// a tile drag; the expanded panel renders outside the grid, so a press there can
|
||||
// only ever reach ECharts. Selection appearance is asserted in the expanded view
|
||||
// for that reason, and the drag rule in the grid view where it actually applies.
|
||||
const GRID_CHART_HOST = '.grid-stack-item [data-test="chart-renderer"]';
|
||||
const EXPANDED_CHART_HOST =
|
||||
'[data-test="dashboard-viewpanel-panel-schema-renderer"] [data-test="chart-renderer"]';
|
||||
|
||||
const BRUSH_START_FRACTION = 0.2;
|
||||
const BRUSH_END_FRACTION = 0.8;
|
||||
|
||||
// Sampled well inside the brushed band so a read never straddles its edges.
|
||||
const TINT_SAMPLE_X = [0.35, 0.65];
|
||||
const TINT_SAMPLE_Y = [0.35, 0.65];
|
||||
|
||||
export default class DashboardZoomDrag {
|
||||
constructor(page) {
|
||||
this.page = page;
|
||||
this.chartHostSelector = GRID_CHART_HOST;
|
||||
this.globalPickerBtn = page.locator(
|
||||
'[data-test="dashboard-global-date-time-picker"] [data-test="date-time-btn"]'
|
||||
);
|
||||
this.expandedCloseBtn = page.locator(
|
||||
'[data-test="dashboard-viewpanel-close-btn"]'
|
||||
);
|
||||
this.expandedRefreshBtn = page.locator(
|
||||
'[data-test="dashboard-viewpanel-refresh-data-btn"]'
|
||||
);
|
||||
}
|
||||
|
||||
get chartRenderer() {
|
||||
return this.page.locator(this.chartHostSelector);
|
||||
}
|
||||
|
||||
get chartCanvas() {
|
||||
return this.page.locator(`${this.chartHostSelector} canvas`);
|
||||
}
|
||||
|
||||
// The GridStack tile for a panel by id — the host carrying the persisted layout.
|
||||
getPanelTile(panelId) {
|
||||
return this.page.locator(`.grid-stack-item[gs-id="${panelId}"]`);
|
||||
}
|
||||
|
||||
// The panel header bar — the GridStack drag handle (the body is `.drag-cancel`).
|
||||
getPanelHeader(panelId) {
|
||||
return this.page.locator(
|
||||
`[data-test-panel-id="${panelId}"] [data-test="dashboard-panel-bar"]`
|
||||
);
|
||||
}
|
||||
|
||||
// The panel body — `.drag-cancel`, so a press here must reach ECharts, not GridStack.
|
||||
getPanelBody(panelId) {
|
||||
return this.page.locator(
|
||||
`[data-test-panel-id="${panelId}"] [data-test="dashboard-panel-body"]`
|
||||
);
|
||||
}
|
||||
|
||||
// The global date-time picker button — the zoom brush's observable surface.
|
||||
getGlobalPickerBtn() {
|
||||
return this.globalPickerBtn;
|
||||
}
|
||||
|
||||
// Expand the panel to the full-window view. The fullscreen button is revealed
|
||||
// on hover, so the panel must be hovered before it can be waited for.
|
||||
async openExpandedPanel(timeout = 30000) {
|
||||
await this.page
|
||||
.locator('[data-test="dashboard-panel-container"]')
|
||||
.first()
|
||||
.hover();
|
||||
const button = this.page
|
||||
.locator('[data-test="dashboard-panel-fullscreen-btn"]')
|
||||
.first();
|
||||
await button.waitFor({ state: "visible", timeout });
|
||||
await button.click();
|
||||
await this.expandedCloseBtn.waitFor({ state: "visible", timeout });
|
||||
this.chartHostSelector = EXPANDED_CHART_HOST;
|
||||
}
|
||||
|
||||
async closeExpandedPanel(timeout = 30000) {
|
||||
await this.expandedCloseBtn.click();
|
||||
await this.expandedCloseBtn.waitFor({ state: "hidden", timeout });
|
||||
this.chartHostSelector = GRID_CHART_HOST;
|
||||
}
|
||||
|
||||
// Re-run the expanded panel's query, which re-applies the chart options and so
|
||||
// dispatches takeGlobalCursor again — the path that used to leak a brush handler.
|
||||
async refreshExpandedPanel(timeout = 30000) {
|
||||
await this.expandedRefreshBtn.waitFor({ state: "visible", timeout });
|
||||
const searched = this.page
|
||||
.waitForResponse((r) => r.url().includes("/_search"), { timeout })
|
||||
.catch(() => null);
|
||||
await this.expandedRefreshBtn.click();
|
||||
await searched;
|
||||
// The re-render keeps the same geometry, so a stale dwell count would pass instantly.
|
||||
await this.page.evaluate(() => {
|
||||
window.__o2ChartBox = null;
|
||||
});
|
||||
}
|
||||
|
||||
// Poll until ECharts has mounted a canvas that has stopped moving. A non-zero
|
||||
// canvas is not enough: the dashboard reflows after load, so a brush path
|
||||
// measured too early is offset by hundreds of pixels and the drag lands off
|
||||
// the chart. Position matters as much as size, and the reflow outlasts a
|
||||
// couple of frames — hence the dwell.
|
||||
async waitForChartMounted(timeout = 30000) {
|
||||
await this.page.waitForFunction(
|
||||
(selector) => {
|
||||
const host = document.querySelector(selector);
|
||||
if (!host || !host.hasAttribute("_echarts_instance_")) return false;
|
||||
const canvas = host.querySelector("canvas");
|
||||
if (!canvas) return false;
|
||||
const rect = canvas.getBoundingClientRect();
|
||||
if (rect.width < 200 || rect.height < 80) return false;
|
||||
const key = [rect.left, rect.top, rect.width, rect.height]
|
||||
.map((n) => Math.round(n))
|
||||
.join(":");
|
||||
const prev = window.__o2ChartBox;
|
||||
window.__o2ChartBox =
|
||||
prev && prev.key === key ? { key, count: prev.count + 1 } : { key, count: 1 };
|
||||
return window.__o2ChartBox.count >= 4;
|
||||
},
|
||||
this.chartHostSelector,
|
||||
{ timeout, polling: 100 }
|
||||
);
|
||||
}
|
||||
|
||||
// Wait for a panel's tile and header bar to be mounted, so the drag handle
|
||||
// actually exists (an off-screen panel renders a placeholder, not the bar).
|
||||
async waitForPanelTile(panelId, timeout = 30000) {
|
||||
await this.getPanelTile(panelId).waitFor({ state: "visible", timeout });
|
||||
await this.getPanelHeader(panelId).waitFor({ state: "visible", timeout });
|
||||
}
|
||||
|
||||
// Read the persisted GridStack layout of a panel off its tile attributes.
|
||||
async readPanelLayout(panelId) {
|
||||
const tile = this.getPanelTile(panelId);
|
||||
const [x, y, w, h] = await Promise.all([
|
||||
tile.getAttribute("gs-x"),
|
||||
tile.getAttribute("gs-y"),
|
||||
tile.getAttribute("gs-w"),
|
||||
tile.getAttribute("gs-h"),
|
||||
]);
|
||||
return { x, y, w, h };
|
||||
}
|
||||
|
||||
// zrender paints the selection cover on the next frame, so a sample taken in
|
||||
// the same tick as the mouse move would read the chart without its overlay.
|
||||
async settleFrames() {
|
||||
await this.page.evaluate(
|
||||
() =>
|
||||
new Promise((resolve) =>
|
||||
requestAnimationFrame(() => requestAnimationFrame(resolve))
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
// Resolve the brush path in viewport coordinates and cache it, so the
|
||||
// hold/release pair drags along exactly the band the tint sampler reads.
|
||||
async resolveBrushPath() {
|
||||
await this.waitForChartMounted();
|
||||
const canvas = this.chartCanvas.first();
|
||||
await canvas.waitFor({ state: "visible", timeout: 30000 });
|
||||
const box = await canvas.boundingBox();
|
||||
if (!box) {
|
||||
throw new Error("chart canvas has no bounding box");
|
||||
}
|
||||
this.brushPath = {
|
||||
startX: box.x + box.width * BRUSH_START_FRACTION,
|
||||
endX: box.x + box.width * BRUSH_END_FRACTION,
|
||||
y: box.y + box.height * 0.5,
|
||||
};
|
||||
return this.brushPath;
|
||||
}
|
||||
|
||||
// Park the cursor at the brush origin without pressing, so the axis pointer is
|
||||
// already painted when the caller samples the pre-drag baseline.
|
||||
async moveToBrushStart() {
|
||||
const { startX, y } = await this.resolveBrushPath();
|
||||
await this.page.mouse.move(startX, y);
|
||||
await this.settleFrames();
|
||||
}
|
||||
|
||||
// Press and drag across the band WITHOUT releasing, leaving the cover painted.
|
||||
async holdBrushAcrossCanvas() {
|
||||
const path = await this.resolveBrushPath();
|
||||
await this.page.mouse.move(path.startX, path.y);
|
||||
await this.page.mouse.down();
|
||||
await this.page.mouse.move(path.endX, path.y, { steps: 12 });
|
||||
await this.settleFrames();
|
||||
}
|
||||
|
||||
async releaseBrush() {
|
||||
await this.page.mouse.up();
|
||||
this.brushPath = null;
|
||||
}
|
||||
|
||||
// Mean (blue - red) over the sampled band. The selection fill is deep sky blue
|
||||
// at low alpha, so the overlay lifts this well clear of the chart's own
|
||||
// baseline, while echarts' default near-neutral grey barely moves it.
|
||||
async measureBandBlueShift() {
|
||||
return this.page.evaluate(
|
||||
({ xs, ys, selector }) => {
|
||||
const host = document.querySelector(selector);
|
||||
if (!host) return null;
|
||||
const layers = Array.from(host.querySelectorAll("canvas"));
|
||||
if (!layers.length) return null;
|
||||
|
||||
// zrender may paint the series and the selection cover on separate
|
||||
// layers, and leaves the plot area itself transparent — so flatten onto
|
||||
// opaque white, or the sampled band is all alpha 0 and carries no colour.
|
||||
const flat = document.createElement("canvas");
|
||||
flat.width = layers[0].width;
|
||||
flat.height = layers[0].height;
|
||||
const flatCtx = flat.getContext("2d");
|
||||
flatCtx.fillStyle = "#ffffff";
|
||||
flatCtx.fillRect(0, 0, flat.width, flat.height);
|
||||
for (const layer of layers) {
|
||||
flatCtx.drawImage(layer, 0, 0, flat.width, flat.height);
|
||||
}
|
||||
|
||||
const x = Math.floor(flat.width * xs[0]);
|
||||
const w = Math.max(1, Math.floor(flat.width * (xs[1] - xs[0])));
|
||||
const y = Math.floor(flat.height * ys[0]);
|
||||
const h = Math.max(1, Math.floor(flat.height * (ys[1] - ys[0])));
|
||||
const { data } = flatCtx.getImageData(x, y, w, h);
|
||||
|
||||
let sum = 0;
|
||||
let counted = 0;
|
||||
for (let i = 0; i < data.length; i += 4) {
|
||||
sum += data[i + 2] - data[i];
|
||||
counted++;
|
||||
}
|
||||
return counted ? sum / counted : null;
|
||||
},
|
||||
{ xs: TINT_SAMPLE_X, ys: TINT_SAMPLE_Y, selector: this.chartHostSelector }
|
||||
);
|
||||
}
|
||||
|
||||
// True while GridStack has a tile drag in flight — stamped on mouse-down only
|
||||
// when the grab point is outside `.drag-cancel`.
|
||||
async isGridDragActive() {
|
||||
return this.page.evaluate(() =>
|
||||
Boolean(
|
||||
document.querySelector(".grid-stack-dragging") ||
|
||||
document.querySelector(".ui-draggable-dragging")
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
// Press on an element and drag by a viewport-pixel offset WITHOUT releasing,
|
||||
// so the caller can observe whether a grid drag actually started.
|
||||
async holdDragFrom(locator, { deltaX, deltaY }) {
|
||||
await locator.waitFor({ state: "visible", timeout: 30000 });
|
||||
await locator.scrollIntoViewIfNeeded();
|
||||
const box = await locator.boundingBox();
|
||||
if (!box) {
|
||||
throw new Error("drag source has no bounding box");
|
||||
}
|
||||
const startX = box.x + box.width / 2;
|
||||
const startY = box.y + box.height / 2;
|
||||
await this.page.mouse.move(startX, startY);
|
||||
await this.page.mouse.down();
|
||||
await this.page.mouse.move(startX + deltaX, startY + deltaY, { steps: 15 });
|
||||
await this.settleFrames();
|
||||
}
|
||||
|
||||
async releaseDrag() {
|
||||
await this.page.mouse.up();
|
||||
}
|
||||
|
||||
// Drag a panel's header bar by a viewport-pixel offset (GridStack snaps to cells).
|
||||
async dragPanelHeader(panelId, { deltaX, deltaY }) {
|
||||
await this.holdDragFrom(this.getPanelHeader(panelId), { deltaX, deltaY });
|
||||
await this.releaseDrag();
|
||||
}
|
||||
|
||||
// Brush a horizontal dataZoom selection across the chart canvas: mouse down at
|
||||
// ~20% width, drag to ~80% width at mid-height, release. The toolbox dataZoom
|
||||
// tool is auto-selected on init, so no toolbox button click is required.
|
||||
async brushChartCanvas() {
|
||||
await this.holdBrushAcrossCanvas();
|
||||
await this.releaseBrush();
|
||||
}
|
||||
}
|
||||
@@ -23,6 +23,7 @@ import DashboardShareExportPage from "./dashboardPages/dashboard-share-export";
|
||||
import DashboardTimeRefresh from "./dashboardPages/dashboard-refresh";
|
||||
import DateTimeHelper from "./dashboardPages/dashboard-time";
|
||||
import DashboardPanelTime from "./dashboardPages/dashboard-panel-time";
|
||||
import DashboardZoomDrag from "./dashboardPages/dashboard-zoom-drag";
|
||||
import LogsVisualise from "./dashboardPages/visualise";
|
||||
import { DashboardPage } from "./dashboardPages/dashboardPage.js";
|
||||
import { ScheduledReportsDrawerPage } from "./dashboardPages/scheduledReportsDrawer.js";
|
||||
@@ -158,6 +159,7 @@ class PageManager {
|
||||
this.dashboardTimeRefresh = new DashboardTimeRefresh(page);
|
||||
this.dateTimeHelper = new DateTimeHelper(page);
|
||||
this.dashboardPanelTime = new DashboardPanelTime(page);
|
||||
this.dashboardZoomDrag = new DashboardZoomDrag(page);
|
||||
this.logsVisualise = new LogsVisualise(page);
|
||||
this.dashboardPage = new DashboardPage(page);
|
||||
this.scheduledReportsDrawer = new ScheduledReportsDrawerPage(page);
|
||||
|
||||
@@ -0,0 +1,301 @@
|
||||
const {
|
||||
test,
|
||||
expect,
|
||||
navigateToBase,
|
||||
} = require("../utils/enhanced-baseFixtures.js");
|
||||
import PageManager from "../../pages/page-manager";
|
||||
import { ingestion } from "./utils/dashIngestion.js";
|
||||
import { cleanupTestDashboard, setupTestDashboard } from "./utils/dashCreation.js";
|
||||
import { generateDashboardName } from "./utils/configPanelHelpers.js";
|
||||
import { createDashboardWithMultiplePanels } from "./utils/panelTimeSetup.js";
|
||||
const testLogger = require("../utils/test-logger.js");
|
||||
|
||||
// The selection fill rgba(0,191,255,0.15) lifts (blue - red) by ~38 over the
|
||||
// chart; echarts' own rgba(234,237,245,0.5) default lifts it by ~5 while washing
|
||||
// the series out, so anything under this floor means the brush style was lost.
|
||||
const MIN_SELECTION_TINT = 20;
|
||||
|
||||
// A brush controller left bound to the same zrender repaints the cover, roughly
|
||||
// doubling that lift — a reading past this ceiling is a stacked overlay.
|
||||
const MAX_SELECTION_TINT = 60;
|
||||
|
||||
test.describe("Dashboard Chart Zoom Brush & Panel Drag testcases", () => {
|
||||
test.describe.configure({ mode: "parallel" });
|
||||
|
||||
test.beforeEach(async ({ page }, testInfo) => {
|
||||
testLogger.testStart(testInfo.title, testInfo.file);
|
||||
await navigateToBase(page);
|
||||
// Deduped per worker inside the helper — the repeat posts add nothing and
|
||||
// cost ~90 s of each test's budget on cloud.
|
||||
await ingestion(page);
|
||||
testLogger.info("Test setup completed");
|
||||
});
|
||||
|
||||
// Build a saved single panel over e2e_automate and return to the dashboard view.
|
||||
async function buildSavedPanel(page, pm, dashboardName, panelName, chartType) {
|
||||
await setupTestDashboard(page, pm, dashboardName);
|
||||
await pm.dashboardCreate.addPanel();
|
||||
await pm.chartTypeSelector.selectChartType(chartType);
|
||||
await pm.chartTypeSelector.selectStreamType("logs");
|
||||
await pm.chartTypeSelector.selectStream("e2e_automate");
|
||||
await pm.chartTypeSelector.removeField("y_axis_1", "y");
|
||||
await pm.chartTypeSelector.searchAndAddField("kubernetes_container_hash", "y");
|
||||
await pm.dashboardPanelActions.addPanelName(panelName);
|
||||
await pm.dashboardPanelActions.applyDashboardBtn();
|
||||
await pm.dashboardPanelActions
|
||||
.waitForChartToRender()
|
||||
.catch((e) => testLogger.warn("waitForChartToRender:", e.message));
|
||||
await pm.dashboardPanelActions.savePanel();
|
||||
}
|
||||
|
||||
test("TC-ZOOM-001: Dragging a brush on a time-series chart switches the dashboard to an absolute range", {
|
||||
tag: ["@dashboard-chart-zoom-brush", "@all", "@functional", "@P0"],
|
||||
}, async ({ page }) => {
|
||||
const pm = new PageManager(page);
|
||||
const dashboardName = generateDashboardName();
|
||||
|
||||
await buildSavedPanel(page, pm, dashboardName, "Zoom Line", "line");
|
||||
testLogger.info("Line chart panel saved on dashboard view");
|
||||
|
||||
// Saved dashboard view: wait for the chart to mount and paint data.
|
||||
await pm.dashboardZoomDrag.waitForChartMounted();
|
||||
await pm.dashboardPanelActions.verifyChartHasData(expect);
|
||||
|
||||
// Initial global time is relative: URL carries period= and no from=/to=.
|
||||
await expect
|
||||
.poll(
|
||||
() => {
|
||||
const url = page.url();
|
||||
return url.includes("period=") && !url.includes("from=") && !url.includes("to=");
|
||||
},
|
||||
{ timeout: 15000 }
|
||||
)
|
||||
.toBe(true);
|
||||
|
||||
// Register a re-query listener before brushing, so we can assert the
|
||||
// dashboard re-ran its panel queries after the zoom (not just the initial load).
|
||||
const searchResponsePromise = page
|
||||
.waitForResponse((r) => r.url().includes("/_search"), { timeout: 30000 })
|
||||
.catch(() => null);
|
||||
|
||||
await pm.dashboardZoomDrag.brushChartCanvas();
|
||||
|
||||
// Vue Router uses replaceState, so poll until the URL flips to absolute.
|
||||
await expect
|
||||
.poll(
|
||||
() => {
|
||||
const url = page.url();
|
||||
return url.includes("from=") && url.includes("to=") && !url.includes("period=");
|
||||
},
|
||||
{ intervals: [100, 200, 500, 1000, 2000], timeout: 30000 }
|
||||
)
|
||||
.toBe(true);
|
||||
|
||||
// The brush must have triggered a dashboard re-query.
|
||||
const searchResponse = await searchResponsePromise;
|
||||
expect(searchResponse).not.toBeNull();
|
||||
|
||||
// The global picker now reflects an absolute range, not a relative "Last …" label.
|
||||
await expect(pm.dashboardZoomDrag.getGlobalPickerBtn()).not.toContainText(/last/i);
|
||||
|
||||
await cleanupTestDashboard(page, pm, dashboardName);
|
||||
});
|
||||
|
||||
test("TC-DRAG-001: Dragging a panel's header bar repositions it and saves the layout", {
|
||||
tag: ["@dashboard-chart-zoom-brush", "@all", "@functional", "@P1"],
|
||||
}, async ({ page }) => {
|
||||
const pm = new PageManager(page);
|
||||
const dashboardName = generateDashboardName();
|
||||
|
||||
const { panelIds } = await createDashboardWithMultiplePanels(page, pm, {
|
||||
dashboardName,
|
||||
panels: [
|
||||
{ panelName: "Drag Panel A", panelTimeEnabled: false },
|
||||
{ panelName: "Drag Panel B", panelTimeEnabled: false },
|
||||
],
|
||||
});
|
||||
const panelBId = panelIds[1];
|
||||
testLogger.info("Two panels created", { panelIds });
|
||||
|
||||
await pm.dashboardZoomDrag.waitForPanelTile(panelBId);
|
||||
const before = await pm.dashboardZoomDrag.readPanelLayout(panelBId);
|
||||
testLogger.info("Pre-drag panel B layout", before);
|
||||
|
||||
// Register the layout-save listener before dragging, so we can assert the
|
||||
// GridStack change handler persisted the new layout to the backend.
|
||||
const putResponsePromise = page
|
||||
.waitForResponse(
|
||||
(r) => r.request().method() === "PUT" && r.url().includes("/dashboards/"),
|
||||
{ timeout: 30000 }
|
||||
)
|
||||
.catch(() => null);
|
||||
|
||||
await pm.dashboardZoomDrag.dragPanelHeader(panelBId, { deltaX: -400, deltaY: 200 });
|
||||
|
||||
const putResponse = await putResponsePromise;
|
||||
expect(putResponse).not.toBeNull();
|
||||
|
||||
const after = await pm.dashboardZoomDrag.readPanelLayout(panelBId);
|
||||
testLogger.info("Post-drag panel B layout", after);
|
||||
expect(after.x !== before.x || after.y !== before.y).toBe(true);
|
||||
|
||||
await cleanupTestDashboard(page, pm, dashboardName);
|
||||
});
|
||||
|
||||
test("TC-ZOOM-002: Dragging on a pie chart does not change the global time range", {
|
||||
tag: ["@dashboard-chart-zoom-brush", "@all", "@functional", "@P2"],
|
||||
}, async ({ page }) => {
|
||||
const pm = new PageManager(page);
|
||||
const dashboardName = generateDashboardName();
|
||||
|
||||
await buildSavedPanel(page, pm, dashboardName, "Pie No Zoom", "pie");
|
||||
testLogger.info("Pie chart panel saved on dashboard view");
|
||||
|
||||
await pm.dashboardZoomDrag.waitForChartMounted();
|
||||
await pm.dashboardPanelActions.verifyChartHasData(expect);
|
||||
|
||||
// Initial global time is relative.
|
||||
await expect
|
||||
.poll(
|
||||
() => {
|
||||
const url = page.url();
|
||||
return url.includes("period=") && !url.includes("from=") && !url.includes("to=");
|
||||
},
|
||||
{ timeout: 15000 }
|
||||
)
|
||||
.toBe(true);
|
||||
|
||||
// Same horizontal drag on the pie canvas — toolbox.show is false for pie,
|
||||
// so no dataZoom event fires and the global time range must stay relative.
|
||||
await pm.dashboardZoomDrag.brushChartCanvas();
|
||||
|
||||
await expect
|
||||
.poll(
|
||||
() => {
|
||||
const url = page.url();
|
||||
return url.includes("period=") && !url.includes("from=") && !url.includes("to=");
|
||||
},
|
||||
{ intervals: [100, 200, 500, 1000, 1000], timeout: 5000 }
|
||||
)
|
||||
.toBe(true);
|
||||
|
||||
await cleanupTestDashboard(page, pm, dashboardName);
|
||||
});
|
||||
|
||||
test("TC-ZOOM-003: Selecting a range on an expanded panel paints the translucent tint, not an opaque grey", {
|
||||
tag: ["@dashboard-chart-zoom-brush", "@all", "@functional", "@P0"],
|
||||
}, async ({ page }) => {
|
||||
const pm = new PageManager(page);
|
||||
const dashboardName = generateDashboardName();
|
||||
|
||||
await buildSavedPanel(page, pm, dashboardName, "Zoom Tint", "line");
|
||||
|
||||
// Expanded view: the chart is outside the GridStack tile, so the press can
|
||||
// only reach ECharts and this measures the selection fill, nothing else.
|
||||
await pm.dashboardZoomDrag.openExpandedPanel();
|
||||
await pm.dashboardZoomDrag.waitForChartMounted();
|
||||
await pm.dashboardPanelActions.verifyChartHasData(expect);
|
||||
|
||||
// Baseline read with the cursor already parked on the canvas, so the axis
|
||||
// pointer is in both samples and only the selection cover differs.
|
||||
await pm.dashboardZoomDrag.moveToBrushStart();
|
||||
const baseline = await pm.dashboardZoomDrag.measureBandBlueShift();
|
||||
expect(baseline).not.toBeNull();
|
||||
|
||||
await pm.dashboardZoomDrag.holdBrushAcrossCanvas();
|
||||
const brushed = await pm.dashboardZoomDrag.measureBandBlueShift();
|
||||
await pm.dashboardZoomDrag.releaseBrush();
|
||||
expect(brushed).not.toBeNull();
|
||||
|
||||
const tint = brushed - baseline;
|
||||
testLogger.info("Selection cover tint", { baseline, brushed, tint });
|
||||
expect(tint).toBeGreaterThan(MIN_SELECTION_TINT);
|
||||
expect(tint).toBeLessThan(MAX_SELECTION_TINT);
|
||||
|
||||
await pm.dashboardZoomDrag.closeExpandedPanel();
|
||||
await cleanupTestDashboard(page, pm, dashboardName);
|
||||
});
|
||||
|
||||
test("TC-ZOOM-004: Repeated re-renders do not stack the selection cover", {
|
||||
tag: ["@dashboard-chart-zoom-brush", "@all", "@functional", "@P1"],
|
||||
}, async ({ page }) => {
|
||||
const pm = new PageManager(page);
|
||||
const dashboardName = generateDashboardName();
|
||||
|
||||
await buildSavedPanel(page, pm, dashboardName, "Zoom Restack", "line");
|
||||
|
||||
await pm.dashboardZoomDrag.openExpandedPanel();
|
||||
await pm.dashboardZoomDrag.waitForChartMounted();
|
||||
await pm.dashboardPanelActions.verifyChartHasData(expect);
|
||||
|
||||
// Every re-apply of the chart options dispatches takeGlobalCursor, the path
|
||||
// that used to bind a fresh brush controller to the same zrender without
|
||||
// unbinding the previous one, so each repaint darkened the cover further.
|
||||
for (let i = 0; i < 3; i++) {
|
||||
await pm.dashboardZoomDrag.refreshExpandedPanel();
|
||||
await pm.dashboardZoomDrag.waitForChartMounted();
|
||||
}
|
||||
|
||||
await pm.dashboardZoomDrag.moveToBrushStart();
|
||||
const baseline = await pm.dashboardZoomDrag.measureBandBlueShift();
|
||||
expect(baseline).not.toBeNull();
|
||||
|
||||
await pm.dashboardZoomDrag.holdBrushAcrossCanvas();
|
||||
const brushed = await pm.dashboardZoomDrag.measureBandBlueShift();
|
||||
await pm.dashboardZoomDrag.releaseBrush();
|
||||
expect(brushed).not.toBeNull();
|
||||
|
||||
const tint = brushed - baseline;
|
||||
testLogger.info("Selection cover tint after repeated re-renders", {
|
||||
baseline,
|
||||
brushed,
|
||||
tint,
|
||||
});
|
||||
expect(tint).toBeGreaterThan(MIN_SELECTION_TINT);
|
||||
expect(tint).toBeLessThan(MAX_SELECTION_TINT);
|
||||
|
||||
await pm.dashboardZoomDrag.closeExpandedPanel();
|
||||
await cleanupTestDashboard(page, pm, dashboardName);
|
||||
});
|
||||
|
||||
test("TC-DRAG-002: Pressing the panel body never starts a grid drag, while the header still does", {
|
||||
tag: ["@dashboard-chart-zoom-brush", "@all", "@functional", "@P1"],
|
||||
}, async ({ page }) => {
|
||||
const pm = new PageManager(page);
|
||||
const dashboardName = generateDashboardName();
|
||||
|
||||
const { panelIds } = await createDashboardWithMultiplePanels(page, pm, {
|
||||
dashboardName,
|
||||
panels: [
|
||||
{ panelName: "Cancel Panel A", panelTimeEnabled: false },
|
||||
{ panelName: "Cancel Panel B", panelTimeEnabled: false },
|
||||
],
|
||||
});
|
||||
const panelBId = panelIds[1];
|
||||
await pm.dashboardZoomDrag.waitForPanelTile(panelBId);
|
||||
|
||||
// Vertical only: a horizontal press on the body would also brush the chart,
|
||||
// and what is under test here is the grid, not the zoom.
|
||||
const gesture = { deltaX: 0, deltaY: -200 };
|
||||
|
||||
await pm.dashboardZoomDrag.holdDragFrom(
|
||||
pm.dashboardZoomDrag.getPanelBody(panelBId),
|
||||
gesture
|
||||
);
|
||||
const bodyStartedDrag = await pm.dashboardZoomDrag.isGridDragActive();
|
||||
await pm.dashboardZoomDrag.releaseDrag();
|
||||
testLogger.info("Grid drag from panel body", { bodyStartedDrag });
|
||||
expect(bodyStartedDrag).toBe(false);
|
||||
|
||||
await pm.dashboardZoomDrag.holdDragFrom(
|
||||
pm.dashboardZoomDrag.getPanelHeader(panelBId),
|
||||
gesture
|
||||
);
|
||||
const headerStartedDrag = await pm.dashboardZoomDrag.isGridDragActive();
|
||||
await pm.dashboardZoomDrag.releaseDrag();
|
||||
testLogger.info("Grid drag from panel header", { headerStartedDrag });
|
||||
expect(headerStartedDrag).toBe(true);
|
||||
|
||||
await cleanupTestDashboard(page, pm, dashboardName);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user