Files
openobserve/tests/ui-testing/pages/dashboardPages/dashCreation.js
T

250 lines
8.8 KiB
JavaScript

import { expect } from "playwright/test";
import logData from "../../fixtures/log.json";
const testLogger = require('../../playwright-tests/utils/test-logger.js');
// Function to wait for the dashboard page to load
export const waitForDashboardPage = async function (page) {
// If already on the dashboard page, skip waiting for navigation
if (!page.url().includes("/web/dashboards")) {
await page.waitForURL(/\/web\/dashboards.*/, { timeout: 30000 });
}
// Additional wait for page to stabilize
await page.waitForTimeout(1000);
// Wait for either the API response or the dashboard table to appear
// Use Promise.race to succeed on whichever happens first
try {
await Promise.race([
// Wait for API response
page.waitForResponse(
(response) =>
/\/api\/.*\/dashboards/.test(response.url()) &&
response.status() === 200,
{ timeout: 30000 }
),
// OR wait for dashboard table to be visible
page.waitForSelector('[data-test="dashboard-table"]', {
state: 'visible',
timeout: 30000
}),
// OR wait for import button to be visible (in case we're on import page)
page.waitForSelector('[data-test="dashboard-import"]', {
state: 'visible',
timeout: 30000
}),
]);
} catch (err) {
// If all options fail, log the error but check if we're actually on the page
testLogger.warn("Dashboard page load check failed, verifying page state", { error: err.message });
// Check if we're actually on the dashboard page by verifying URL
if (page.url().includes("/web/dashboards")) {
testLogger.info("URL indicates we're on dashboard page, continuing");
} else {
throw new Error("Dashboard page did not load as expected: " + err.message);
}
}
// Final wait for stability
await page.waitForTimeout(1000);
};
export const applyQueryButton = async function (page) {
const search = page.waitForResponse(logData.applyQuery);
await page.waitForTimeout(3000);
await page.locator("[data-test='logs-search-bar-refresh-btn']").click({
force: true,
});
await expect.poll(async () => (await search).status()).toBe(200);
};
export async function deleteDashboard(page, dashboardName) {
testLogger.info('Deleting dashboard', { dashboardName });
// Wait for page to be fully loaded
// Wait for either the Dashboard API or Folder API (whichever comes first)
await Promise.race([
page.waitForResponse(
(response) => {
const url = response.url();
return (
( /\/api\/.*\/dashboards/.test(url) ||
/\/api\/.*\/folders/.test(url) ) &&
response.status() === 200
);
},
{ timeout: 20000 }
),
page.waitForSelector('[data-test="dashboard-table"]', { timeout: 20000 }),
]);
// const dashboardRow = page.locator(`//tr[.//td[text()="${dashboardName}"]]`);
// await expect(dashboardRow).toBeVisible(); // Ensure the row is visible
const dashboardRow = page
.locator('//tr[.//div[@title="' + dashboardName + '"]]')
.nth(0);
const deleteButton = dashboardRow.locator('[data-test="dashboard-delete"]');
await deleteButton.click();
// Wait for the confirm button to be visible (dialog teleports content to body; text element
// may be in DOM but hidden during animation — confirm button visibility is more reliable)
await page.locator('[data-test="o-dialog-primary-btn"]').waitFor({
state: 'visible',
timeout: 10000
});
// Wait for button to be truly stable using waitForFunction
await page.waitForFunction(
() => {
const dialog = document.querySelector('[data-test="dashboard-confirm-dialog"]');
if (!dialog) return false;
// Find the confirm button with data-test attribute inside dialog
const button = dialog.querySelector('[data-test="o-dialog-primary-btn"]');
if (!button) return false;
// Check if button is stable (has computed style and is not animating)
const rect = button.getBoundingClientRect();
const isStable = rect.width > 0 && rect.height > 0 &&
button.offsetParent !== null &&
!button.disabled &&
window.getComputedStyle(button).visibility === 'visible';
return isStable;
},
undefined,
{ timeout: 15000, polling: 100 }
);
testLogger.debug('Confirm button found and is stable');
// Additional small wait for any final animations
await page.waitForTimeout(500);
// Set up API listener BEFORE clicking
const deleteResponsePromise = page.waitForResponse(
async (response) => {
const url = response.url();
const method = response.request().method();
// Check if this is a DELETE request to dashboards endpoint
const isDeleteEndpoint = method === 'DELETE' &&
/\/api\/.*\/dashboards\/\d+/.test(url);
if (isDeleteEndpoint) {
testLogger.debug(`Delete API called: ${url} - Status: ${response.status()}`);
// Verify response body contains success message
try {
const body = await response.json();
if (body.code === 200 && body.message === 'Dashboard deleted') {
testLogger.info('Delete API confirmed: Dashboard deleted');
return true;
}
} catch (e) {
// If we can't parse JSON, just check status code
return response.status() === 200 || response.status() === 204;
}
}
return false;
},
{ timeout: 20000 }
).catch((error) => {
testLogger.warn(`Delete API timeout: ${error.message}`);
return null; // Return null if timeout, don't throw
});
// Click the button using evaluate to avoid detachment issues
await page.evaluate(() => {
const dialog = document.querySelector('[data-test="dashboard-confirm-dialog"]');
const button = dialog?.querySelector('[data-test="o-dialog-primary-btn"]');
if (button) {
button.click();
}
});
testLogger.info('Clicked confirm button using evaluate');
// Wait for API response
const deleteResponse = await deleteResponsePromise;
if (deleteResponse) {
testLogger.info('Dashboard deleted successfully');
}
// Verify the success message appears
await page.getByText("Dashboard deleted successfully").waitFor({
state: 'visible',
timeout: 10000
}).catch(() => {
testLogger.info('Success message not visible or disappeared quickly');
});
// Ensure the dashboard row is removed from the table
// await expect(dashboardRow).not.toBeVisible({ timeout: 5000 });
}
/**
* Create a minimal v8 dashboard via API for use as a report source.
* Uses the apiCleanup instance for authenticated requests.
* @param {Object} api - apiCleanup instance
* @param {string} dashboardName - Unique name for the dashboard
* @param {string} folderId - Folder ID (defaults to "default")
* @returns {Promise<Object>} { success, dashboard, error }
*/
export async function createDashboardViaApi(api, dashboardName, folderId = 'default') {
testLogger.info('Creating dashboard via API', { dashboardName, folderId });
try {
const payload = {
version: 8,
title: dashboardName,
description: 'Auto-created for E2E test',
tabs: [{
tabId: 'default',
name: 'Default',
panels: []
}]
};
const response = await api._fetch(
`${api.baseUrl}/api/${api.org}/dashboards?folder=${encodeURIComponent(folderId)}`,
{
method: 'POST',
headers: {
'Authorization': api.authHeader,
'Content-Type': 'application/json'
},
body: JSON.stringify(payload)
}
);
if (!response.ok) {
const errorBody = await response.text();
testLogger.error('Failed to create dashboard via API', { dashboardName, status: response.status, body: errorBody });
return { success: false, error: `HTTP ${response.status}: ${errorBody}` };
}
const result = await response.json();
// MetaDashboard nests the actual data under the version key (e.g., "v8")
const inner = result[`v${result.version}`] || result;
const tabs = (inner.tabs || [{ tabId: 'default', name: 'Default' }]).map(t => ({
tab_id: t.tabId || t.tab_id || 'default'
}));
const dashboard = {
dashboard_id: inner.dashboardId || inner.dashboard_id,
title: inner.title,
tabs
};
testLogger.info('Dashboard created via API', { dashboardName, dashboardId: dashboard.dashboard_id });
return { success: true, dashboard };
} catch (error) {
testLogger.error('Failed to create dashboard via API', { dashboardName, error: error.message });
return { success: false, error: error.message };
}
}