From 9ac5a0b002371ec252a4c60fed23ffbc9dc91aa5 Mon Sep 17 00:00:00 2001 From: Victor Marin <36818606+mdvictor@users.noreply.github.com> Date: Fri, 26 Sep 2025 10:09:20 +0300 Subject: [PATCH] Dashboards: Add network call checks for reloadable dashboards in E2Es (#111380) * Add network call verification for reloadable dashboards * refactor --- .../dashboard-navigation.spec.ts | 10 ++- .../dashboard-cujs/dashboard-view.spec.ts | 8 ++- e2e-playwright/dashboard-cujs/utils.ts | 72 +++++++++++++++++++ 3 files changed, 87 insertions(+), 3 deletions(-) diff --git a/e2e-playwright/dashboard-cujs/dashboard-navigation.spec.ts b/e2e-playwright/dashboard-cujs/dashboard-navigation.spec.ts index c454bc69291..008ca89ef7f 100644 --- a/e2e-playwright/dashboard-cujs/dashboard-navigation.spec.ts +++ b/e2e-playwright/dashboard-cujs/dashboard-navigation.spec.ts @@ -11,7 +11,7 @@ import { getScopesDashboardsSearchInput, getScopesSelectorInput, } from './cuj-selectors'; -import { getConfigDashboards } from './utils'; +import { checkDashboardReloadBehavior, getConfigDashboards, trackDashboardReloadRequests } from './utils'; test.use({ featureToggles: { @@ -118,8 +118,14 @@ test.describe( await groupByVariable.press('Escape'); await expect(scopesDashboards.first()).toBeVisible(); + + const { getRequests, waitForExpectedRequests } = await trackDashboardReloadRequests(page); await scopesDashboards.first().click(); - await page.waitForURL('**/d/**'); + await waitForExpectedRequests(); + await page.waitForLoadState('networkidle'); + + const requests = getRequests(); + expect(checkDashboardReloadBehavior(requests)).toBe(true); //all values are set after dashboard switch await expect(markdownContent).toContainText(`GroupByVar: dev\n\nAdHocVar: ${processedPills}`); diff --git a/e2e-playwright/dashboard-cujs/dashboard-view.spec.ts b/e2e-playwright/dashboard-cujs/dashboard-view.spec.ts index 9fccafada83..53dd02a0314 100644 --- a/e2e-playwright/dashboard-cujs/dashboard-view.spec.ts +++ b/e2e-playwright/dashboard-cujs/dashboard-view.spec.ts @@ -1,6 +1,6 @@ import { test, expect } from '@grafana/plugin-e2e'; -import { getConfigDashboards } from './utils'; +import { getConfigDashboards, trackDashboardReloadRequests, checkDashboardReloadBehavior } from './utils'; test.use({ featureToggles: { @@ -27,7 +27,13 @@ test.describe( for (const db of dashboards) { await test.step('1.Loads dashboard successfully - ' + db, async () => { + const { getRequests, waitForExpectedRequests } = await trackDashboardReloadRequests(page); const dashboardPage = await gotoDashboardPage({ uid: db }); + await waitForExpectedRequests(); + await page.waitForLoadState('networkidle'); + + const requests = getRequests(); + expect(checkDashboardReloadBehavior(requests)).toBe(true); const panelTitle = dashboardPage.getByGrafanaSelector( selectors.components.Panels.Panel.title(PANEL_UNDER_TEST) diff --git a/e2e-playwright/dashboard-cujs/utils.ts b/e2e-playwright/dashboard-cujs/utils.ts index 393aee8b5f5..f12f4960fae 100644 --- a/e2e-playwright/dashboard-cujs/utils.ts +++ b/e2e-playwright/dashboard-cujs/utils.ts @@ -5,6 +5,8 @@ import * as path from 'path'; const USE_LIVE_DATA = Boolean(process.env.API_CONFIG_PATH); const API_CONFIG_PATH = process.env.API_CONFIG_PATH ?? '../dashboards/cujs/config.json'; +const RELOADABLE_DASHBOARD_REQUEST_NO = 2; + async function loadApiConfig() { const configPath = path.resolve(__dirname, API_CONFIG_PATH); @@ -63,3 +65,73 @@ export async function prepareAPIMocks(page: Page) { return apiConfig; } + +interface DashboardRequest { + url: string; + timestamp: number; + response: { metadata: { annotations: string[] } }; +} + +export async function trackDashboardReloadRequests(page: Page): Promise<{ + getRequests: () => DashboardRequest[]; + waitForExpectedRequests: () => Promise; +}> { + const dashboardRequests: DashboardRequest[] = []; + let resolveWhenComplete: () => void; + let expectedRequestCount = 1; //initial request that gives us the meta param + + const completionPromise = new Promise((resolve) => { + resolveWhenComplete = resolve; + }); + + const handler = async (route) => { + const response = await route.fetch(); + const responseJson = await response.json(); + const isFirstRequest = dashboardRequests.length === 0; + + dashboardRequests.push({ + url: route.request().url(), + timestamp: Date.now(), + response: responseJson, + }); + + // After first request, check if we should expect more + if (isFirstRequest) { + const hasReloadAnnotation = responseJson?.metadata?.annotations?.['grafana.app/reloadOnParamsChange'] === 'true'; + + if (hasReloadAnnotation) { + expectedRequestCount = RELOADABLE_DASHBOARD_REQUEST_NO; + } + } + + // Resolve if we've reached the expected count + if (dashboardRequests.length >= expectedRequestCount) { + resolveWhenComplete(); + } + + await route.fulfill({ response }); + }; + + await page.route('**/dashboards/**/dto?**', handler); + + return { + getRequests: () => dashboardRequests, + waitForExpectedRequests: () => completionPromise, + }; +} + +export function checkDashboardReloadBehavior(requests: DashboardRequest[]): boolean { + if (requests.length === 0) { + return false; + } + + const firstRequest = requests[0]; + const hasReloadAnnotation = + firstRequest?.response?.metadata?.annotations?.['grafana.app/reloadOnParamsChange'] === 'true'; + + if (hasReloadAnnotation) { + return requests.length === RELOADABLE_DASHBOARD_REQUEST_NO; + } else { + return requests.length === 1; + } +}