diff --git a/.github/workflows/playwright.yml b/.github/workflows/playwright.yml index ea140792a5..ca81a164b0 100644 --- a/.github/workflows/playwright.yml +++ b/.github/workflows/playwright.yml @@ -168,6 +168,7 @@ jobs: "dashboard-folder.spec.js", "dashboard-import.spec.js", "maxquery.spec.js", + "dashboard-chartJson.spec.js", ] - testfolder: "Dashboards-Settings" browser: "chrome" diff --git a/tests/ui-testing/pages/apiCleanup.js b/tests/ui-testing/pages/apiCleanup.js index 815eec0127..75c3edc407 100644 --- a/tests/ui-testing/pages/apiCleanup.js +++ b/tests/ui-testing/pages/apiCleanup.js @@ -2611,4 +2611,4 @@ class APICleanup { } } -module.exports = APICleanup; +module.exports = APICleanup; \ No newline at end of file diff --git a/tests/ui-testing/pages/dashboardPages/dashboard-panel-actions.js b/tests/ui-testing/pages/dashboardPages/dashboard-panel-actions.js index b995355021..1af5558fc4 100644 --- a/tests/ui-testing/pages/dashboardPages/dashboard-panel-actions.js +++ b/tests/ui-testing/pages/dashboardPages/dashboard-panel-actions.js @@ -37,18 +37,35 @@ export default class DashboardactionPage { async waitForChartToRender() { // Wait for button or parent to have light button class (render complete) - // Supports both repo structures: button.o2-primary-button-light OR parent.o2-secondary-button-light + // The parent div.q-btn-group has classes o2-primary-button and o2-secondary-button-light await this.page.waitForFunction(() => { const btn = document.querySelector('[data-test="dashboard-apply"]'); - return ( - btn && - (btn.classList.contains("o2-primary-button-light") || - btn.classList.contains("o2-secondary-button-light") || - (btn.parentElement && - (btn.parentElement.classList.contains("o2-primary-button-light") || - btn.parentElement.classList.contains("o2-secondary-button-light")))) - ); - }); + if (!btn) return false; + + // Check button itself + if (btn.classList.contains("o2-primary-button-light") || + btn.classList.contains("o2-secondary-button-light")) { + return true; + } + + // Check immediate parent (button group container) + const parent = btn.parentElement; + if (parent && + (parent.classList.contains("o2-primary-button-light") || + parent.classList.contains("o2-secondary-button-light"))) { + return true; + } + + // Check if parent has both o2-primary-button AND o2-secondary-button-light + // (which indicates the button group is in the rendered state) + if (parent && + parent.classList.contains("o2-primary-button") && + parent.classList.contains("o2-secondary-button-light")) { + return true; + } + + return false; + }, { timeout: 30000 }); } //Dashboard panel actions(Edit, Layout, Duplicate, Inspector, Move, Delete) diff --git a/tests/ui-testing/pages/dashboardPages/dashboard-variables.js b/tests/ui-testing/pages/dashboardPages/dashboard-variables.js index c0d9896a40..9c5af4a422 100644 --- a/tests/ui-testing/pages/dashboardPages/dashboard-variables.js +++ b/tests/ui-testing/pages/dashboardPages/dashboard-variables.js @@ -2,6 +2,7 @@ //methods: addDashboardVariable, selectValueFromVariableDropDown //addDashboardVariable params: name, streamtype, streamName, field, customValueSearch, filterConfig, showMultipleValues import { expect } from "@playwright/test"; +import { waitForValuesStreamComplete } from "../../playwright-tests/utils/streaming-helpers.js"; export default class DashboardVariables { constructor(page) { @@ -56,13 +57,11 @@ export default class DashboardVariables { await this.page .getByRole("option", { name: streamName, exact: true }) .click(); - // Select Field const fieldSelect = await this.page.locator('[data-test="dashboard-variable-field-select"]'); await fieldSelect.click(); - await this.page.keyboard.type(field); + await this.page.keyboard.type(field, {delay:100}); - // Wait for dropdown to have options available await this.page.waitForFunction( () => { @@ -225,9 +224,25 @@ export default class DashboardVariables { async selectValueFromVariableDropDown(label, value) { const input = this.page.getByLabel(label, { exact: true }); await input.waitFor({ state: "visible", timeout: 10000 }); + + // Wait for _values_stream API call when clicking on the dropdown + // Start listening before the action to ensure we capture the stream + const valuesStreamPromise = waitForValuesStreamComplete(this.page); + await input.click(); + + // Wait for the values stream to complete (waits for 'data: [[DONE]]' marker) + await valuesStreamPromise; + + // Wait for _values_stream API call when filling/searching + // Start listening before filling to capture the search stream + // const searchStreamPromise = waitForValuesStreamComplete(this.page); + await input.fill(value); + // // Wait for search stream to complete (waits for 'data: [[DONE]]' marker) + // await searchStreamPromise; + const option = this.page.getByRole("option", { name: value }); await option.waitFor({ state: "visible", timeout: 10000 }); await option.click(); diff --git a/tests/ui-testing/playwright-tests/cleanup.spec.js b/tests/ui-testing/playwright-tests/cleanup.spec.js index df69e4517a..d2a0cb66f8 100644 --- a/tests/ui-testing/playwright-tests/cleanup.spec.js +++ b/tests/ui-testing/playwright-tests/cleanup.spec.js @@ -196,3 +196,4 @@ test.describe("Pre-Test Cleanup", () => { testLogger.info('Pre-test cleanup completed successfully'); }); }); + diff --git a/tests/ui-testing/playwright-tests/dashboards/dashbaord-chartJson.spec.js b/tests/ui-testing/playwright-tests/dashboards/dashboard-chartJson.spec.js similarity index 100% rename from tests/ui-testing/playwright-tests/dashboards/dashbaord-chartJson.spec.js rename to tests/ui-testing/playwright-tests/dashboards/dashboard-chartJson.spec.js diff --git a/tests/ui-testing/playwright-tests/dashboards/dashboard-geoMap.spec.js b/tests/ui-testing/playwright-tests/dashboards/dashboard-geoMap.spec.js index 6be78e14b7..4ec19e9ad1 100644 --- a/tests/ui-testing/playwright-tests/dashboards/dashboard-geoMap.spec.js +++ b/tests/ui-testing/playwright-tests/dashboards/dashboard-geoMap.spec.js @@ -76,7 +76,7 @@ test.describe("dashboard maps testcases", () => { ); await waitForDateTimeButtonToBeEnabled(page); - await pm.dashboardTimeRefresh.setRelative("6", "w"); + await pm.dashboardTimeRefresh.setRelative("30", "m"); // await pm.dashboardPanelActions.applyDashboardBtn(); await pm.dashboardPanelActions.waitForChartToRender(); diff --git a/tests/ui-testing/playwright-tests/dashboards/dashboard-html-chart.spec.js b/tests/ui-testing/playwright-tests/dashboards/dashboard-html-chart.spec.js index ae9623147b..a6cd7d12b0 100644 --- a/tests/ui-testing/playwright-tests/dashboards/dashboard-html-chart.spec.js +++ b/tests/ui-testing/playwright-tests/dashboards/dashboard-html-chart.spec.js @@ -136,7 +136,7 @@ test.describe("HTML chart dashboard", () => { await pm.chartTypeSelector.selectChartType("html"); - await pm.dashboardTimeRefresh.setRelative("15", "m"); + await pm.dashboardTimeRefresh.setRelative("30", "m"); await page .locator('[data-test="dashboard-html-editor"]') diff --git a/tests/ui-testing/playwright-tests/dashboards/dashboard-multi-y-axis.spec.js b/tests/ui-testing/playwright-tests/dashboards/dashboard-multi-y-axis.spec.js index 60525c9c9a..075180cdc8 100644 --- a/tests/ui-testing/playwright-tests/dashboards/dashboard-multi-y-axis.spec.js +++ b/tests/ui-testing/playwright-tests/dashboards/dashboard-multi-y-axis.spec.js @@ -59,7 +59,7 @@ test.describe("dashboard multi y axis testcases", () => { await pm.chartTypeSelector.searchAndAddField("kubernetes_labels_name", "b"); // Set the relative time range - await pm.dateTimeHelper.setRelativeTimeRange("6-w"); + await pm.dateTimeHelper.setRelativeTimeRange("30-m"); // Apply the configuration and wait for the chart to render await pm.dashboardPanelActions.applyDashboardBtn(); @@ -122,7 +122,7 @@ test.describe("dashboard multi y axis testcases", () => { await pm.chartTypeSelector.searchAndAddField("kubernetes_labels_name", "b"); // Set the relative time range - await pm.dateTimeHelper.setRelativeTimeRange("6-w"); + await pm.dateTimeHelper.setRelativeTimeRange("30-m"); // Apply the configuration and wait for the chart to render await pm.dashboardPanelActions.applyDashboardBtn(); diff --git a/tests/ui-testing/playwright-tests/dashboards/dashboard-streaming.spec.js b/tests/ui-testing/playwright-tests/dashboards/dashboard-streaming.spec.js index 8b763dc172..1686278c21 100644 --- a/tests/ui-testing/playwright-tests/dashboards/dashboard-streaming.spec.js +++ b/tests/ui-testing/playwright-tests/dashboards/dashboard-streaming.spec.js @@ -87,21 +87,53 @@ test.describe("dashboard streaming testcases", () => { ); await pm.dashboardPanelActions.applyDashboardBtn(); + await pm.dashboardPanelActions.waitForChartToRender(); + await waitForDateTimeButtonToBeEnabled(page); - await pm.dashboardTimeRefresh.setRelative("6", "w"); + await pm.dashboardTimeRefresh.setRelative("30", "m"); + + await pm.dashboardPanelActions.waitForChartToRender(); + const variableInput = page.getByLabel("variablename", { exact: true, }); await variableInput.waitFor({ state: "visible", timeout: 10000 }); + + // Wait for the _values API call when clicking on the variable input + const valuesApiPromise = page.waitForResponse( + response => response.url().includes('_values') || response.url().includes('/values'), + { timeout: 15000 } + ); + await variableInput.click(); + // Wait for the API response to complete + try { + await valuesApiPromise; + await page.waitForTimeout(500); // Additional wait for dropdown to render + } catch (e) { + console.log("Warning: _values API response not detected, continuing..."); + } + // Type partial search terms to trigger multiple _values API calls with streaming enabled const searchTerms = ["zi", "zio", "ziox"]; for (const term of searchTerms) { + // Wait for the _values API call for each search term + const valuesApiResponse = page.waitForResponse( + response => response.url().includes('_values') || response.url().includes('/values'), + { timeout: 15000 } + ); + await variableInput.fill(term); - // await page.waitForTimeout(1500); - // await page.waitForLoadState("networkidle"); + + // Wait for the API response to complete + try { + await valuesApiResponse; + await page.waitForTimeout(300); // Wait for dropdown to update with new results + } catch (e) { + console.log(`Warning: _values API response not detected for term "${term}", continuing...`); + } } // Select the final value const option = page.getByRole("option", { name: "ziox" }); @@ -262,7 +294,7 @@ test.describe("dashboard streaming testcases", () => { await pm.dashboardPanelActions.applyDashboardBtn(); await waitForDateTimeButtonToBeEnabled(page); - await pm.dashboardTimeRefresh.setRelative("6", "w"); + await pm.dashboardTimeRefresh.setRelative("30", "m"); await pm.dashboardPanelActions.waitForChartToRender(); diff --git a/tests/ui-testing/playwright-tests/dashboards/dashboard-transpose.spec.js b/tests/ui-testing/playwright-tests/dashboards/dashboard-transpose.spec.js index 06d45ba225..2869d07e2e 100644 --- a/tests/ui-testing/playwright-tests/dashboards/dashboard-transpose.spec.js +++ b/tests/ui-testing/playwright-tests/dashboards/dashboard-transpose.spec.js @@ -352,7 +352,7 @@ test.describe("dashboard UI testcases", () => { await pm.chartTypeSelector.searchAndAddField("xAxis", "y"); // Set relative time range - await pm.dashboardTimeRefresh.setRelative("6", "w"); + await pm.dashboardTimeRefresh.setRelative("30", "m"); await pm.dashboardPanelActions.waitForChartToRender(); diff --git a/tests/ui-testing/playwright-tests/dashboards/dashboard.spec.js b/tests/ui-testing/playwright-tests/dashboards/dashboard.spec.js index 78af95e55a..a5a5b62c31 100644 --- a/tests/ui-testing/playwright-tests/dashboards/dashboard.spec.js +++ b/tests/ui-testing/playwright-tests/dashboards/dashboard.spec.js @@ -419,7 +419,7 @@ test.describe("dashboard UI testcases", () => { ); // Set date-time and timezone for gauge chart - await pm.dateTimeHelper.setRelativeTimeRange("6-w"); + await pm.dateTimeHelper.setRelativeTimeRange("15-m"); await pm.dashboardPanelActions.applyDashboardBtn(); // Verify the gauge chart is visible @@ -429,7 +429,7 @@ test.describe("dashboard UI testcases", () => { await pm.chartTypeSelector.selectChartType("table"); // Set timezone for the table chart - await pm.dateTimeHelper.setRelativeTimeRange("1-w"); + await pm.dateTimeHelper.setRelativeTimeRange("15-m"); await pm.dashboardPanelActions.applyDashboardBtn(); await pm.dashboardPanelActions.waitForChartToRender(); @@ -471,7 +471,7 @@ test.describe("dashboard UI testcases", () => { ); // Set date-time filter - await pm.dateTimeHelper.setRelativeTimeRange("5-w"); + await pm.dateTimeHelper.setRelativeTimeRange("15-m"); await pm.dashboardPanelActions.applyDashboardBtn(); @@ -521,7 +521,7 @@ test.describe("dashboard UI testcases", () => { // Set date-time filter and timezone await waitForDateTimeButtonToBeEnabled(page); - await pm.dashboardTimeRefresh.setRelative("6", "w"); + await pm.dashboardTimeRefresh.setRelative("30", "m"); await pm.dashboardPanelActions.applyDashboardBtn(); // Attempt to save the panel without a name @@ -579,7 +579,7 @@ test.describe("dashboard UI testcases", () => { await pm.chartTypeSelector.searchAndAddField("kubernetes_host", "filter"); // Set time range and apply - await pm.dateTimeHelper.setRelativeTimeRange("5-w"); + await pm.dateTimeHelper.setRelativeTimeRange("15-m"); await pm.dashboardPanelActions.applyDashboardBtn(); // Apply "Is Null" filter diff --git a/tests/ui-testing/playwright-tests/dashboards/dashboard2.spec.js b/tests/ui-testing/playwright-tests/dashboards/dashboard2.spec.js index dc66aadb33..9ff3460535 100644 --- a/tests/ui-testing/playwright-tests/dashboards/dashboard2.spec.js +++ b/tests/ui-testing/playwright-tests/dashboards/dashboard2.spec.js @@ -101,7 +101,7 @@ test.describe("dashboard UI testcases", () => { // Set relative time await waitForDateTimeButtonToBeEnabled(page); - await pm.dashboardTimeRefresh.setRelative("6", "w"); + await pm.dashboardTimeRefresh.setRelative("30", "m"); await pm.dashboardPanelActions.applyDashboardBtn(); // Cancel adding the breakdown field @@ -155,7 +155,7 @@ test.describe("dashboard UI testcases", () => { // Set the date-time range and apply changes await waitForDateTimeButtonToBeEnabled(page); - await pm.dashboardTimeRefresh.setRelative("6", "w"); + await pm.dashboardTimeRefresh.setRelative("30", "m"); await pm.dashboardPanelActions.applyDashboardBtn(); // Change chart types and verify @@ -220,7 +220,7 @@ test.describe("dashboard UI testcases", () => { // Set the date-time range and apply changes await waitForDateTimeButtonToBeEnabled(page); - await pm.dashboardTimeRefresh.setRelative("6", "w"); + await pm.dashboardTimeRefresh.setRelative("30", "m"); await pm.dashboardPanelActions.applyDashboardBtn(); // Save the dashboard panel @@ -261,7 +261,7 @@ test.describe("dashboard UI testcases", () => { await pm.chartTypeSelector.searchAndAddField("kubernetes_host", "b"); // Set the date-time range - await pm.dashboardTimeRefresh.setRelative("6", "w"); + await pm.dashboardTimeRefresh.setRelative("30", "m"); await pm.dashboardPanelActions.applyDashboardBtn(); // Refresh the page @@ -307,7 +307,7 @@ test.describe("dashboard UI testcases", () => { // Set relative date-time and timezone await waitForDateTimeButtonToBeEnabled(page); - await pm.dashboardTimeRefresh.setRelative("6", "w"); + await pm.dashboardTimeRefresh.setRelative("30", "m"); // await pm.dashboardPanelActions.applyDashboardBtn(); @@ -393,7 +393,7 @@ test.describe("dashboard UI testcases", () => { // Set relative date-time and timezone await waitForDateTimeButtonToBeEnabled(page); - await pm.dashboardTimeRefresh.setRelative("6", "w"); + await pm.dashboardTimeRefresh.setRelative("30", "m"); await pm.dashboardPanelActions.applyDashboardBtn(); await pm.dashboardPanelActions.savePanel(); @@ -436,7 +436,7 @@ test.describe("dashboard UI testcases", () => { // Set relative date-time await waitForDateTimeButtonToBeEnabled(page); - await pm.dashboardTimeRefresh.setRelative("6", "w"); + await pm.dashboardTimeRefresh.setRelative("30", "m"); // Configure no value replacement await pm.dashboardPanelConfigs.openConfigPanel(); diff --git a/tests/ui-testing/playwright-tests/utils/streaming-helpers.js b/tests/ui-testing/playwright-tests/utils/streaming-helpers.js index 34bf41c63a..8ee6ed1b2e 100644 --- a/tests/ui-testing/playwright-tests/utils/streaming-helpers.js +++ b/tests/ui-testing/playwright-tests/utils/streaming-helpers.js @@ -6,13 +6,18 @@ const testLogger = require('./test-logger.js'); * * Usage: * ```javascript - * import { waitForStreamComplete, waitForTableWithData } from '../utils/streaming-helpers.js'; + * import { waitForStreamComplete, waitForValuesStreamComplete, waitForTableWithData } from '../utils/streaming-helpers.js'; * * // Wait for a streaming API to complete * const streamPromise = waitForStreamComplete(page); * await someAction(); * await streamPromise; * + * // Wait for values stream to complete + * const valuesStreamPromise = waitForValuesStreamComplete(page); + * await page.click('#field-dropdown'); + * await valuesStreamPromise; + * * // Wait for table to have data * await waitForTableWithData(page); * ``` @@ -81,6 +86,65 @@ export async function waitForStreamComplete(page, timeout = 15000) { ]); } +/** + * Waits for a values stream API call to complete by listening for the 'data: [[DONE]]' marker. + * This is specifically designed for the _values_stream endpoint. + * + * The function monitors the _values_stream endpoint and resolves when it receives + * the completion marker 'data: [[DONE]]' in the response body. + * + * @param {import('@playwright/test').Page} page - The Playwright page object + * @param {number} timeout - Maximum time to wait in milliseconds (default: 15000) + * @returns {Promise} Resolves when values stream completes or times out + * + * @example + * // Start listening before triggering the request + * const streamPromise = waitForValuesStreamComplete(page); + * await page.click('#field-dropdown'); + * await streamPromise; // Wait for values streaming to finish + */ +export async function waitForValuesStreamComplete(page, timeout = 15000) { + return Promise.race([ + new Promise((resolve) => { + const responseHandler = async (response) => { + const url = response.url(); + + // Check if this is a values stream endpoint + if (url.includes('_values_stream')) { + + try { + // Read the response body as text + const body = await response.text(); + + // Check if stream has completed with 'data: [[DONE]]' marker + if (body.includes('data: [[DONE]]')) { + testLogger.info(`Values stream completed for: ${url.split('/').pop()}`); + page.off('response', responseHandler); + resolve(); + } + } catch (error) { + // Response body might not be available or already consumed + testLogger.debug(`Could not read response body: ${error.message}`); + } + } + }; + + page.on('response', responseHandler); + + // Clean up listener if it somehow doesn't resolve + setTimeout(() => { + page.off('response', responseHandler); + }, timeout + 1000); + }), + new Promise((resolve) => { + setTimeout(() => { + testLogger.warn('Values stream wait timeout - continuing anyway'); + resolve(); + }, timeout); + }) + ]); +} + /** * Waits for the table to have data rows before proceeding. * This ensures the table has been populated with actual data from the stream.