mirror of
https://github.com/openobserve/openobserve.git
synced 2026-11-06 00:09:22 +00:00
## What changed
Naming an alert, panel, workflow, pipeline or function meant inventing a
name up front in a form field before anything else could be configured.
Two pieces replace that.
**Inline rename** — the page title *is* the input. `OInlineEdit` /
`OFormInlineEdit` (`web/src/lib/forms/InlineEdit`): click to rename,
Enter or blur commits, Escape restores the pre-edit value. A `#trail`
slot carries the "Auto" badge in display mode only.
**Auto-generated names** — `utils/autoName.ts` builds the name,
`composables/useAutoName.ts` decides when it's allowed to win. A name is
AUTO until the user types, then it's theirs for good; clearing the field
and blurring re-arms auto. The generators describe only what is actually
configured, so a half-built panel yields a shorter name rather than a
speculative one, and an empty string when there is nothing to say.
- Panel names read as prose — `Avg of duration by service`, `Record
count`, `k8s_logs overview`. Both live panel-field shapes are handled
(builder `functionName` + `args`, and legacy flat `column` +
`aggregationFunction`); the time column is read from
`zoConfig.timestamp_column` rather than hardcoded. Hand-written queries
fall back to the stream, since naming a panel after `x_axis_1` is worse
than saying nothing.
- Alert names are identifiers — `anomaly_k8s_logs`,
`k8s_logs_status_gte_500` — built to survive the backend's
`RE_OFGA_UNSUPPORTED_NAME` rule, with operators spelled out and a
`slugify` backstop. A leading underscore is preserved so internal
streams (`_rumdata`) keep their names.
### Supporting library changes
| Component | Change |
| --- | --- |
| `OPageHeader` / `OPageLayout` | New `titleOverflow="visible"` — the
`<h1>`'s `truncate` clips an interactive title's focus ring and the
error message it floats below itself |
| `OSelect` | New `appearance="inline"` — borderless, no fixed height,
sized to content, for a value that reads as a word inside running text
("Add Alert **in KTX**") |
All new strings go through i18n (`common.inlineEdit.*`,
`dashboard.autoName.*`, `alerts.autoName.*`, per-module `renameHint`).
Interpolated identifiers — column names, SQL functions, stream names —
stay verbatim.
## Wired into
AddPanel, AddAlert (+ `useAlertForm`), WorkflowEditor, PipelineEditor,
Functions / FunctionsToolbar, IncidentDetailDrawer,
InlineSelectFolderDropdown.
## Tested
244 unit tests across the 5 affected suites, all passing — including new
specs for `OInlineEdit`, `useAutoName`, and `autoName`.
One pre-existing expectation in `autoName.spec.ts` was updated: it
asserted `Avg of duration, max of duration +1 more`, capitalising only
the first word of a multi-measure name. `measureLabel` capitalises per
measure by design, so the expectation now reads `Avg of duration, Max of
duration +1 more`.
---------
Co-authored-by: ktx-vaidehi <134508096+ktx-vaidehi@users.noreply.github.com>
Co-authored-by: ktx-vaidehi <vaidehi.akhani@kiara.tech>
602 lines
22 KiB
JavaScript
602 lines
22 KiB
JavaScript
/**
|
|
* Panel Time Test Setup Utilities
|
|
* Provides reusable functions for creating dashboards and panels with panel time configurations
|
|
*/
|
|
|
|
import { expect } from "playwright/test";
|
|
import testLogger from '../../utils/test-logger.js';
|
|
import { waitForDashboardPage, deleteDashboard } from './dashCreation.js';
|
|
|
|
/**
|
|
* Start creating a panel (opens add panel form, fills basic fields) WITHOUT saving.
|
|
* Use this when you need to perform actions in Add Panel mode before saving.
|
|
* @param {Object} page - Playwright page object
|
|
* @param {Object} pm - PageManager instance
|
|
* @param {Object} config - Configuration object
|
|
* @param {string} config.dashboardName - Dashboard name
|
|
* @param {string} config.panelName - Panel name
|
|
* @param {string} config.stream - Stream name (optional, default: "e2e_automate")
|
|
* @param {string} config.streamType - Stream type (optional, default: "logs")
|
|
*/
|
|
export async function startPanelCreation(page, pm, config) {
|
|
const {
|
|
dashboardName,
|
|
panelName = 'Test Panel',
|
|
stream = 'e2e_automate',
|
|
streamType = 'logs'
|
|
} = config;
|
|
|
|
testLogger.info('Starting panel creation (without saving)', { dashboardName, panelName });
|
|
|
|
// Navigate to dashboards
|
|
await pm.dashboardList.menuItem("dashboards-item");
|
|
await waitForDashboardPage(page);
|
|
await pm.dashboardCreate.waitForDashboardUIStable();
|
|
|
|
// Create dashboard
|
|
await pm.dashboardCreate.createDashboard(dashboardName);
|
|
await page.locator('[data-test="dashboard-if-no-panel-add-panel-btn"]').waitFor({
|
|
state: "visible",
|
|
timeout: 10000
|
|
});
|
|
|
|
// Click add panel
|
|
await page.locator('[data-test="dashboard-if-no-panel-add-panel-btn"]').click();
|
|
await page.waitForLoadState('networkidle', { timeout: 5000 }).catch(() => {});
|
|
|
|
// Wait for AddPanel view to load
|
|
await page.locator('[data-test="dashboard-panel-name"]').waitFor({
|
|
state: "visible",
|
|
timeout: 10000
|
|
});
|
|
|
|
// Set panel name — inline-edited title (OFormInlineEdit): click the trigger to
|
|
// open the editor, then fill the revealed input.
|
|
await page.locator('[data-test="dashboard-panel-name-trigger"]').click();
|
|
await page.locator('[data-test="dashboard-panel-name-input"]').waitFor({ state: 'visible', timeout: 10000 });
|
|
await page.locator('[data-test="dashboard-panel-name-input"]').fill(panelName);
|
|
await page.waitForLoadState('networkidle', { timeout: 3000 }).catch(() => {});
|
|
|
|
// Select stream type and stream
|
|
await pm.chartTypeSelector.selectStreamType(streamType);
|
|
await pm.chartTypeSelector.selectStream(stream);
|
|
|
|
// Add fields for proper chart configuration
|
|
// remove the auto-seeded default y-axis before adding this panel's measure
|
|
await pm.chartTypeSelector.removeField("y_axis_1", "y");
|
|
await pm.chartTypeSelector.searchAndAddField("kubernetes_container_name", "y");
|
|
await pm.chartTypeSelector.searchAndAddField("kubernetes_namespace_name", "b");
|
|
|
|
testLogger.info('Panel creation form ready (unsaved)', { dashboardName, panelName });
|
|
}
|
|
|
|
/**
|
|
* Create a dashboard with a panel that has panel time configured
|
|
* @param {Object} page - Playwright page object
|
|
* @param {Object} pm - PageManager instance
|
|
* @param {Object} config - Configuration object
|
|
* @param {string} config.dashboardName - Dashboard name
|
|
* @param {string} config.panelName - Panel name (optional, default: "Test Panel")
|
|
* @param {boolean} config.panelTimeEnabled - Enable panel time toggle (optional, default: true)
|
|
* @param {string} config.panelTimeRange - Time range for panel config (optional, default: "1-h"). Pass null to enable toggle without setting time.
|
|
* @param {string} config.stream - Stream name (optional, default: "e2e_automate")
|
|
* @param {string} config.streamType - Stream type (optional, default: "logs")
|
|
* @returns {Promise<Object>} - {dashboardName, panelId}
|
|
*/
|
|
export async function createDashboardWithPanelTime(page, pm, config) {
|
|
const {
|
|
dashboardName,
|
|
panelName = 'Test Panel',
|
|
panelTimeEnabled = true,
|
|
panelTimeRange = '1-h',
|
|
stream = 'e2e_automate',
|
|
streamType = 'logs'
|
|
} = config;
|
|
|
|
testLogger.info('Creating dashboard with panel time', {
|
|
dashboardName,
|
|
panelName,
|
|
panelTimeEnabled,
|
|
panelTimeRange
|
|
});
|
|
|
|
// Navigate to dashboards
|
|
await pm.dashboardList.menuItem("dashboards-item");
|
|
await waitForDashboardPage(page);
|
|
await pm.dashboardCreate.waitForDashboardUIStable();
|
|
|
|
// Create dashboard
|
|
await pm.dashboardCreate.createDashboard(dashboardName);
|
|
await page.locator('[data-test="dashboard-if-no-panel-add-panel-btn"]').waitFor({
|
|
state: "visible",
|
|
timeout: 10000
|
|
});
|
|
|
|
// Add panel with panel time configuration
|
|
const panelId = await addPanelWithPanelTime(page, pm, {
|
|
panelName,
|
|
panelTimeEnabled,
|
|
panelTimeRange,
|
|
stream,
|
|
streamType
|
|
});
|
|
|
|
testLogger.info('Dashboard created successfully', { dashboardName, panelId });
|
|
|
|
return { dashboardName, panelId };
|
|
}
|
|
|
|
/**
|
|
* Add a panel with panel time configuration to existing dashboard
|
|
* @param {Object} page - Playwright page object
|
|
* @param {Object} pm - PageManager instance
|
|
* @param {Object} config - Panel configuration
|
|
* @param {string} config.panelName - Panel name (optional, default: "Test Panel")
|
|
* @param {boolean} config.panelTimeEnabled - Enable panel time toggle (optional, default: true)
|
|
* @param {string} config.panelTimeRange - Time range for panel config (optional, default: "1-h"). Pass null to enable toggle without setting time.
|
|
* @param {string} config.stream - Stream name (optional, default: "e2e_automate")
|
|
* @param {string} config.streamType - Stream type (optional, default: "logs")
|
|
* @returns {Promise<string>} - Panel ID
|
|
*/
|
|
export async function addPanelWithPanelTime(page, pm, config) {
|
|
const {
|
|
panelName = 'Test Panel',
|
|
panelTimeEnabled = true,
|
|
panelTimeRange = '1-h',
|
|
stream = 'e2e_automate',
|
|
streamType = 'logs'
|
|
} = config;
|
|
|
|
testLogger.info('Adding panel with panel time', { panelName, panelTimeEnabled, panelTimeRange });
|
|
|
|
// Click add panel button - handle both cases (no panels vs existing panels)
|
|
const noPanelBtn = page.locator('[data-test="dashboard-if-no-panel-add-panel-btn"]');
|
|
const addPanelBtn = page.locator('[data-test="dashboard-panel-add"]');
|
|
|
|
// Wait for one of the two buttons to become visible (dashboard may still be loading after save)
|
|
await Promise.race([
|
|
addPanelBtn.waitFor({ state: "visible", timeout: 15000 }),
|
|
noPanelBtn.waitFor({ state: "visible", timeout: 15000 })
|
|
]);
|
|
|
|
// Now check which one is actually visible and click it
|
|
if (await addPanelBtn.isVisible().catch(() => false)) {
|
|
await addPanelBtn.click();
|
|
} else {
|
|
await noPanelBtn.click();
|
|
}
|
|
await page.waitForLoadState('networkidle', { timeout: 5000 }).catch(() => {});
|
|
|
|
// Wait for AddPanel view to load
|
|
await page.locator('[data-test="dashboard-panel-name"]').waitFor({
|
|
state: "visible",
|
|
timeout: 10000
|
|
});
|
|
|
|
// Set panel name — inline-edited title (OFormInlineEdit): click the trigger to
|
|
// open the editor, then fill the revealed input.
|
|
await page.locator('[data-test="dashboard-panel-name-trigger"]').click();
|
|
await page.locator('[data-test="dashboard-panel-name-input"]').waitFor({ state: 'visible', timeout: 10000 });
|
|
await page.locator('[data-test="dashboard-panel-name-input"]').fill(panelName);
|
|
|
|
// Wait for UI to settle after filling panel name
|
|
await page.waitForLoadState('networkidle', { timeout: 3000 }).catch(() => {});
|
|
|
|
// Select stream type and stream using PageManager methods
|
|
await pm.chartTypeSelector.selectStreamType(streamType);
|
|
await pm.chartTypeSelector.selectStream(stream);
|
|
|
|
// Add fields to Y and breakdown axes for proper chart configuration
|
|
// remove the auto-seeded default y-axis before adding this panel's measure
|
|
await pm.chartTypeSelector.removeField("y_axis_1", "y");
|
|
await pm.chartTypeSelector.searchAndAddField("kubernetes_container_name", "y");
|
|
await pm.chartTypeSelector.searchAndAddField("kubernetes_namespace_name", "b");
|
|
|
|
// Configure panel time if enabled (v4.0: useDefaultTime toggle + optional "+Set" button)
|
|
if (panelTimeEnabled) {
|
|
// Open config panel sidebar
|
|
await pm.dashboardPanelConfigs.openConfigPanel();
|
|
|
|
// Enable "Use Default Time" toggle
|
|
await pm.dashboardPanelTime.enablePanelTime();
|
|
|
|
// If panelTimeRange is provided, click "+Set" button and configure time
|
|
if (panelTimeRange) {
|
|
// Click "+Set" button to open date time picker
|
|
const setButton = page.locator('[data-test="dashboard-config-set-panel-time"]');
|
|
await setButton.waitFor({ state: "visible", timeout: 5000 });
|
|
await setButton.click();
|
|
|
|
// Select time in the config picker
|
|
await pm.dashboardPanelTime.setPanelTimeRelative(panelTimeRange);
|
|
|
|
// Click the global Apply button (required for config time changes in v4.0)
|
|
await page.locator('[data-test="dashboard-apply"]').click();
|
|
await page.waitForLoadState('networkidle', { timeout: 3000 }).catch(() => {});
|
|
}
|
|
// If panelTimeRange is null: toggle is ON but no time set, follows global time
|
|
}
|
|
|
|
// Save panel and get panel ID using common function
|
|
// Use -1 to get the last panel (the one we just added)
|
|
const panelId = await savePanelAndGetId(page, { panelIndex: -1 });
|
|
|
|
// Wait for the Add Panel button to be ready for the next panel addition
|
|
// This ensures the dashboard is fully transitioned back to view mode
|
|
await page.waitForLoadState('networkidle', { timeout: 3000 }).catch(() => {});
|
|
|
|
testLogger.info('Panel added successfully', { panelId, panelName });
|
|
|
|
return panelId;
|
|
}
|
|
|
|
/**
|
|
* Create a dashboard with multiple panels having different time configurations
|
|
* @param {Object} page - Playwright page object
|
|
* @param {Object} pm - PageManager instance
|
|
* @param {Object} config - Configuration object
|
|
* @param {string} config.dashboardName - Dashboard name
|
|
* @param {Array} config.panels - Array of panel configurations
|
|
* @returns {Promise<Object>} - {dashboardName, panelIds: []}
|
|
*/
|
|
export async function createDashboardWithMultiplePanels(page, pm, config) {
|
|
const { dashboardName, panels } = config;
|
|
|
|
testLogger.info('Creating dashboard with multiple panels', { dashboardName, panelCount: panels.length });
|
|
|
|
// Navigate to dashboards and create
|
|
await pm.dashboardList.menuItem("dashboards-item");
|
|
await waitForDashboardPage(page);
|
|
await pm.dashboardCreate.waitForDashboardUIStable();
|
|
await pm.dashboardCreate.createDashboard(dashboardName);
|
|
|
|
await page.locator('[data-test="dashboard-if-no-panel-add-panel-btn"]').waitFor({
|
|
state: "visible",
|
|
timeout: 10000
|
|
});
|
|
|
|
const panelIds = [];
|
|
|
|
// Add each panel
|
|
for (let i = 0; i < panels.length; i++) {
|
|
const panelConfig = panels[i];
|
|
testLogger.info(`Adding panel ${i + 1}/${panels.length}`, { panelName: panelConfig.panelName });
|
|
|
|
const panelId = await addPanelWithPanelTime(page, pm, panelConfig);
|
|
panelIds.push(panelId);
|
|
|
|
// Wait for panel to stabilize before adding next one
|
|
await page.waitForLoadState('networkidle', { timeout: 3000 }).catch(() => {});
|
|
}
|
|
|
|
testLogger.info('All panels created successfully', { dashboardName, panelIds });
|
|
|
|
return { dashboardName, panelIds };
|
|
}
|
|
|
|
/**
|
|
* Open dashboard by name from dashboard list
|
|
* @param {Object} page - Playwright page object
|
|
* @param {string} dashboardName - Dashboard name
|
|
*/
|
|
export async function openDashboard(page, dashboardName) {
|
|
testLogger.info('Opening dashboard', { dashboardName });
|
|
|
|
// Find and click the dashboard name cell — XPath with element-tag predicates is forbidden
|
|
const dashboardNameCell = page.locator(`[data-test="dashboard-name-cell-${dashboardName}"]`).first();
|
|
await dashboardNameCell.waitFor({ state: "visible", timeout: 10000 });
|
|
await dashboardNameCell.click();
|
|
|
|
// Wait for dashboard to load
|
|
await page.waitForLoadState('networkidle', { timeout: 5000 }).catch(() => {});
|
|
await page.locator('[data-test^="dashboard-panel-"]').first().waitFor({
|
|
state: "visible",
|
|
timeout: 15000
|
|
}).catch(() => {
|
|
testLogger.debug('No panels found in dashboard, might be empty');
|
|
});
|
|
|
|
testLogger.info('Dashboard opened successfully', { dashboardName });
|
|
}
|
|
|
|
/**
|
|
* Edit a panel in dashboard
|
|
* @param {Object} page - Playwright page object
|
|
* @param {string} panelName - Panel name
|
|
*/
|
|
export async function editPanel(page, panelName) {
|
|
testLogger.info('Editing panel', { panelName });
|
|
|
|
// Click panel edit button
|
|
await page.locator(`[data-test="dashboard-edit-panel-${panelName}-dropdown"]`).click();
|
|
await page.locator('[data-test="dashboard-edit-panel"]').waitFor({ state: "visible", timeout: 10000 });
|
|
await page.locator('[data-test="dashboard-edit-panel"]').click();
|
|
await page.locator('[data-test="dashboard-panel-name"]').waitFor({ state: "visible", timeout: 10000 });
|
|
|
|
testLogger.info('Panel edit mode opened', { panelName });
|
|
}
|
|
|
|
/**
|
|
* Save panel after editing
|
|
* @param {Object} page - Playwright page object
|
|
*/
|
|
export async function savePanel(page) {
|
|
testLogger.info('Saving panel');
|
|
|
|
// Ensure any open menus/dropdowns are closed before clicking save
|
|
// This prevents the save button from being intercepted by overlays
|
|
// Wait for ODropdown date-time menu and portal menus to be hidden
|
|
await page.locator('#date-time-menu').first().waitFor({ state: "hidden", timeout: 3000 }).catch(() => {});
|
|
|
|
// Also wait for any date picker portal menus to close (reka-ui portalled content)
|
|
await page.locator('[data-reka-popper-content-wrapper]').first().waitFor({ state: "hidden", timeout: 3000 }).catch(() => {});
|
|
|
|
// Click somewhere neutral to dismiss any remaining overlays (like date picker)
|
|
await page.locator('[data-test="dashboard-panel-name"]').click().catch(() => {});
|
|
|
|
await page.locator('[data-test="dashboard-panel-save"]').click();
|
|
|
|
// Wait for AddPanel form to be dismissed — confirms we've left edit mode
|
|
// Without this, the broad [data-test^="dashboard-panel-"] selector below would
|
|
// immediately match "dashboard-panel-save" (still visible in AddPanel) and return
|
|
// before the transition to dashboard view is complete.
|
|
await page.locator('[data-test="dashboard-panel-name"]').waitFor({
|
|
state: "hidden",
|
|
timeout: 10000
|
|
}).catch(() => {});
|
|
|
|
await page.waitForLoadState('networkidle', { timeout: 10000 }).catch(() => {});
|
|
|
|
// Wait for at least one dashboard panel to be visible (confirms dashboard view)
|
|
await page.locator('[data-test^="dashboard-panel-"]').first().waitFor({
|
|
state: "visible",
|
|
timeout: 15000
|
|
});
|
|
|
|
testLogger.info('Panel saved successfully');
|
|
}
|
|
|
|
/**
|
|
* Save panel and capture panel ID from API response
|
|
* @param {Object} page - Playwright page object
|
|
* @param {Object} options - Options object
|
|
* @param {number} options.panelIndex - Panel index to extract (0-based). Use -1 for last panel. Default: -1
|
|
* @returns {Promise<string>} - Panel ID
|
|
*/
|
|
export async function savePanelAndGetId(page, options = {}) {
|
|
const { panelIndex = -1 } = options;
|
|
|
|
testLogger.info('Saving panel and capturing ID from API response', { panelIndex });
|
|
|
|
// Setup API response listener before saving
|
|
const saveResponsePromise = page.waitForResponse(
|
|
response => response.url().includes('/api/') &&
|
|
response.url().includes('/dashboards/') &&
|
|
response.request().method() === 'PUT',
|
|
{ timeout: 30000 }
|
|
);
|
|
|
|
// Click save button
|
|
await page.locator('[data-test="dashboard-panel-save"]').click();
|
|
|
|
// Wait for the save to complete
|
|
await page.waitForLoadState('networkidle', { timeout: 10000 }).catch(() => {});
|
|
|
|
// Wait for transition back to dashboard view (add panel form should be hidden)
|
|
await page.locator('[data-test="dashboard-panel-name"]').waitFor({
|
|
state: "hidden",
|
|
timeout: 10000
|
|
}).catch(() => {});
|
|
|
|
// Get panel ID from API response
|
|
let panelId;
|
|
try {
|
|
const saveResponse = await saveResponsePromise;
|
|
const responseBody = await saveResponse.json();
|
|
|
|
// Extract panel ID from response structure: response.v8.tabs[0].panels[index].id
|
|
if (responseBody.v8 &&
|
|
responseBody.v8.tabs &&
|
|
responseBody.v8.tabs.length > 0 &&
|
|
responseBody.v8.tabs[0].panels &&
|
|
responseBody.v8.tabs[0].panels.length > 0) {
|
|
const panels = responseBody.v8.tabs[0].panels;
|
|
// Use specified index, or last panel if index is -1
|
|
const targetIndex = panelIndex >= 0 ? panelIndex : panels.length - 1;
|
|
panelId = panels[targetIndex].id;
|
|
testLogger.info('Panel ID from API response', { panelId, panelIndex: targetIndex });
|
|
} else {
|
|
throw new Error('Invalid API response structure');
|
|
}
|
|
} catch (error) {
|
|
// Fallback to DOM extraction if API response fails
|
|
testLogger.warn('Failed to get panel ID from API, falling back to DOM', { error: error.message });
|
|
const fallbackIndex = panelIndex >= 0 ? panelIndex : 0;
|
|
panelId = await getPanelId(page, fallbackIndex);
|
|
testLogger.info('Panel ID from DOM (fallback)', { panelId });
|
|
}
|
|
|
|
return panelId;
|
|
}
|
|
|
|
/**
|
|
* Get panel ID from panel element (by index)
|
|
* @param {Object} page - Playwright page object
|
|
* @param {number} panelIndex - Panel index (0-based)
|
|
* @returns {Promise<string>} - Panel ID
|
|
*/
|
|
export async function getPanelId(page, panelIndex = 0) {
|
|
const panels = page.locator('[data-test^="dashboard-panel-"]');
|
|
const panelCount = await panels.count();
|
|
|
|
if (panelCount === 0) {
|
|
throw new Error('No panels found on dashboard');
|
|
}
|
|
|
|
if (panelIndex >= panelCount) {
|
|
throw new Error(`Panel index ${panelIndex} out of range. Only ${panelCount} panels found.`);
|
|
}
|
|
|
|
const panel = panels.nth(panelIndex);
|
|
const dataTest = await panel.getAttribute('data-test');
|
|
const panelId = dataTest.replace('dashboard-panel-', '');
|
|
|
|
testLogger.debug('Got panel ID', { panelIndex, panelId });
|
|
return panelId;
|
|
}
|
|
|
|
/**
|
|
* Navigate to dashboard URL with specific parameters
|
|
* @param {Object} page - Playwright page object
|
|
* @param {Object} params - URL parameters
|
|
* @param {string} params.dashboardId - Dashboard ID
|
|
* @param {Object} params.panelTimes - Panel time parameters (e.g., {panel1: "1h", panel2: "7d"})
|
|
* @param {string} params.globalTime - Global time parameter (e.g., "15m")
|
|
*/
|
|
export async function navigateToDashboardWithParams(page, params) {
|
|
const { dashboardId, panelTimes = {}, globalTime } = params;
|
|
|
|
let url = `/web/dashboards/view?dashboard=${dashboardId}`;
|
|
|
|
if (globalTime) {
|
|
url += `&period=${globalTime}`;
|
|
}
|
|
|
|
Object.keys(panelTimes).forEach(panelId => {
|
|
url += `&panel-time-${panelId}=${panelTimes[panelId]}`;
|
|
});
|
|
|
|
testLogger.info('Navigating to dashboard with params', { url });
|
|
|
|
await page.goto(url);
|
|
await page.waitForLoadState('networkidle', { timeout: 5000 }).catch(() => {});
|
|
|
|
testLogger.info('Dashboard loaded with params');
|
|
}
|
|
|
|
/**
|
|
* Get dashboard ID from current URL
|
|
* @param {Object} page - Playwright page object
|
|
* @returns {string} - Dashboard ID
|
|
*/
|
|
export function getDashboardIdFromURL(page) {
|
|
const url = new URL(page.url());
|
|
const dashboardId = url.searchParams.get('dashboard');
|
|
|
|
if (!dashboardId) {
|
|
throw new Error('Dashboard ID not found in URL');
|
|
}
|
|
|
|
return dashboardId;
|
|
}
|
|
|
|
/**
|
|
* Wait for all panels to finish loading
|
|
* @param {Object} page - Playwright page object
|
|
* @param {number} timeout - Timeout in milliseconds
|
|
*/
|
|
export async function waitForAllPanelsToLoad(page, timeout = 15000) {
|
|
testLogger.info('Waiting for all panels to load');
|
|
|
|
// Wait for network idle
|
|
await page.waitForLoadState('networkidle', { timeout }).catch(() => {
|
|
testLogger.debug('Network did not become idle, continuing anyway');
|
|
});
|
|
|
|
// Wait for any loading indicators to disappear
|
|
const loadingIndicators = page.locator('[data-test$="-loading"]');
|
|
const count = await loadingIndicators.count();
|
|
|
|
if (count > 0) {
|
|
testLogger.debug(`Found ${count} loading indicators, waiting for them to disappear`);
|
|
for (let i = 0; i < count; i++) {
|
|
await loadingIndicators.nth(i).waitFor({ state: "hidden", timeout: 5000 }).catch(() => {});
|
|
}
|
|
}
|
|
|
|
testLogger.info('All panels loaded');
|
|
}
|
|
|
|
/**
|
|
* Clean up dashboard after test
|
|
* @param {Object} page - Playwright page object
|
|
* @param {Object} pm - PageManager instance
|
|
* @param {string} dashboardName - Dashboard name
|
|
*/
|
|
export async function cleanupDashboard(page, pm, dashboardName) {
|
|
testLogger.info('Cleaning up dashboard', { dashboardName });
|
|
|
|
try {
|
|
// Navigate back to dashboard list
|
|
await pm.dashboardCreate.backToDashboardList();
|
|
await page.locator('[data-test="dashboard-search"]').waitFor({
|
|
state: "visible",
|
|
timeout: 10000
|
|
});
|
|
await page.waitForLoadState('networkidle', { timeout: 3000 }).catch(() => {});
|
|
|
|
// Delete dashboard
|
|
await deleteDashboard(page, dashboardName);
|
|
|
|
testLogger.info('Dashboard cleaned up successfully', { dashboardName });
|
|
} catch (error) {
|
|
testLogger.error('Failed to cleanup dashboard', { dashboardName, error: error.message });
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Verify panel time configuration in panel config (v4.0)
|
|
* @param {Object} page - Playwright page object
|
|
* @param {Object} expectedConfig - Expected configuration
|
|
* @param {boolean} expectedConfig.panelTimeEnabled - Should panel time toggle be enabled
|
|
* @param {string} expectedConfig.panelTimeRange - Expected time range displayed in picker (optional)
|
|
*/
|
|
export async function verifyPanelTimeConfig(page, expectedConfig) {
|
|
const { panelTimeEnabled, panelTimeRange } = expectedConfig;
|
|
|
|
testLogger.info('Verifying panel time config (v4.0)', expectedConfig);
|
|
|
|
// Check if toggle is in correct state
|
|
const toggleLocator = page.locator('[data-test="dashboard-config-allow-panel-time"]');
|
|
const isChecked = await toggleLocator.getAttribute('aria-checked');
|
|
|
|
if (panelTimeEnabled) {
|
|
expect(isChecked).toBe('true');
|
|
|
|
// Verify time range is displayed in picker if provided
|
|
if (panelTimeRange) {
|
|
const pickerBtn = page.locator('[data-test="dashboard-config-panel-time-picker"]');
|
|
const pickerText = await pickerBtn.textContent();
|
|
testLogger.debug('Panel time picker shows', { pickerText });
|
|
// Note: Exact text verification depends on how the time is displayed
|
|
// The picker should show the configured time range
|
|
} else {
|
|
// If panelTimeRange is null, verify "+Set" button is visible
|
|
const setButton = page.locator('[data-test="dashboard-config-set-panel-time"]');
|
|
await expect(setButton).toBeVisible();
|
|
}
|
|
} else {
|
|
expect(isChecked).toBe('false');
|
|
}
|
|
|
|
testLogger.info('Panel time config verified successfully (v4.0)');
|
|
}
|
|
|
|
export default {
|
|
startPanelCreation,
|
|
createDashboardWithPanelTime,
|
|
addPanelWithPanelTime,
|
|
createDashboardWithMultiplePanels,
|
|
openDashboard,
|
|
editPanel,
|
|
savePanel,
|
|
savePanelAndGetId,
|
|
getPanelId,
|
|
navigateToDashboardWithParams,
|
|
getDashboardIdFromURL,
|
|
waitForAllPanelsToLoad,
|
|
cleanupDashboard,
|
|
verifyPanelTimeConfig
|
|
};
|