mirror of
https://github.com/openobserve/openobserve.git
synced 2026-11-05 16:10:43 +00:00
## Summary Completes the Sentinel **Page Object Model** compliance sweep of the Playwright suite (follow-up to #13727, which covered the non-Dashboards shards). Every raw `page.locator(...)` / `page.getBy*(...)` selector — in **actions and assertions** — is relocated out of the spec files into the relevant page object; specs now call page-object methods/getters only. **Result: 0 raw fixture-`page` selectors across the entire `playwright-tests` suite** (single-line *and* multi-line, verified with a dotall/multiline scanner). Together with #13727, the whole suite is POM-clean. ## What's in this PR **Dashboards shards — ~1,460 selectors** relocated into their page objects (`dashboard-panel-configs/actions/edit/time`, `dashboard-chart`, `dashboard-variables-scoped`, `dashboard-filter`, `dashboard-drilldown`, `crossLinkPage`, `visualise`, `dashboard-share-export`, `dashboard-create`, `dashboard-settings`, `dashboard-variables`): | Shard | selectors | |-------|----------:| | Dashboard-Config-Settings (11 specs) | ~255 | | Dashboards-Variables (12 specs, 4 batches) | ~693 | | Dashboards-Tables (7 specs) | ~127 | | Dashboards-Charts (9 specs) | ~81 | | Dashboards-Core + Visualize (10 specs) | ~144 | | Dashboards-Settings + Panel-DateTime (8 specs) | ~107 | | Dashboard-Regression (1 spec) | ~52 | **Correlation UI specs** (3 new `Correlation/*.ui.spec.js` that arrived via main's merge, after the original audit) — converted into a new `correlationDrawerPage` + an extended `correlationSettingsPage`, with `PageManager` wired into specs that previously used none. **Audit stragglers** the original **single-line** audit grep undercounted — multi-line `page\n.locator(...)` chains in `logstable.spec.js` (→ `logsPage.getTableHeaderByField()`) and `dashboard-streaming.spec.js` (2 add-panel selectors). **AI-review fixes** (DeepSeek, on this PR) — removed 2 dead-code duplicate getters + 1 redundant method alias (merge artifacts from the sequential batch commits). One Node-context `console.log` (`dashboard-general-setting`'s `beforeEach`) → `testLogger.info`. ## Approach Nearly every Dashboards spec shares the same page objects, so the work was done in **sequential batches** (one committed, verified unit at a time) to keep page-object edits conflict-free. 13 commits, mapping 1:1 to those batches. ## Not changed (by design) - **`waitForTimeout`** left as-is — logged in the audit, not rewritten (per direction). - **Browser-context code inside `page.evaluate()` / `waitForFunction()`** (`document.querySelector`, `window.*`) left as-is — it runs in the page's JS engine, not Node, so `testLogger` (a Node module) can't be called there. *No `console.*` remained in dashboards specs*; the Node-context ones were converted to `testLogger`. - **`newPage.locator(...)`** on separate popup `Page` instances left as-is (not the fixture `page`). ## Verification - **0 raw fixture-`page` selectors** across all specs — single- and multi-line, comments stripped. - All changed `.js` files pass `node --check`. - **CI-gated** for browser execution (no local runs), consistent with #13727. The enterprise E2E crosscheck already passed against this PR. ## Stats 82 files changed (+3,355 / −2,211) — 61 specs, 21 page objects. Selectors **moved into page objects, not deleted**.
397 lines
14 KiB
JavaScript
397 lines
14 KiB
JavaScript
const {
|
|
test,
|
|
expect,
|
|
navigateToBase,
|
|
} = require("../utils/enhanced-baseFixtures.js");
|
|
const testLogger = require("../utils/test-logger.js");
|
|
import { ingestion } from "./utils/dashIngestion.js";
|
|
import logData from "../../fixtures/log.json";
|
|
import PageManager from "../../pages/page-manager";
|
|
import { waitForDateTimeButtonToBeEnabled } from "../../pages/dashboardPages/dashboard-time";
|
|
import DashboardPanelConfigs from "../../pages/dashboardPages/dashboard-panel-configs";
|
|
|
|
import { deleteDashboard } from "./utils/dashCreation.js";
|
|
|
|
//Dashboard name
|
|
const randomDashboardName =
|
|
"Dashboard_" + Math.random().toString(36).slice(2, 11);
|
|
|
|
// Panel name
|
|
const panelName = "Panel_" + Math.random().toString(36).slice(2, 11);
|
|
|
|
test.describe.configure({ mode: "parallel" });
|
|
|
|
// Stream name used across tests
|
|
const STREAM_NAME = "e2e_automate";
|
|
// Add a global SQL query constant that can be reused across tests
|
|
const largeDatasetSqlQuery = `SELECT kubernetes_annotations_kubectl_kubernetes_io_default_container as "x_axis_1",
|
|
count(kubernetes_container_hash) as "y_axis_1",
|
|
count(kubernetes_container_name) as "y_axis_2",
|
|
count(kubernetes_host) as "y_axis_3",
|
|
count(kubernetes_labels_app_kubernetes_io_instance) as "y_axis_4",
|
|
count(kubernetes_labels_app_kubernetes_io_name) as "y_axis_5",
|
|
count(kubernetes_labels_app_kubernetes_io_version) as "y_axis_6",
|
|
count(kubernetes_labels_operator_prometheus_io_name) as "y_axis_7",
|
|
count(kubernetes_labels_prometheus) as "y_axis_8",
|
|
kubernetes_labels_statefulset_kubernetes_io_pod_name as "breakdown_1"
|
|
FROM "${STREAM_NAME}"
|
|
WHERE kubernetes_namespace_name IS NOT NULL
|
|
GROUP BY x_axis_1, breakdown_1`;
|
|
|
|
const histogramQuery = `SELECT histogram(_timestamp) as "x_axis_1", count(kubernetes_namespace_name) as "y_axis_1" FROM "${STREAM_NAME}" GROUP BY x_axis_1 ORDER BY x_axis_1 ASC `;
|
|
|
|
const histogramQueryWithHaving = `SELECT histogram(_timestamp) as "x_axis_1", count(_timestamp) as "y_axis_1" FROM "${STREAM_NAME}" GROUP BY x_axis_1 HAVING y_axis_1 >= 1000 ORDER BY x_axis_1 ASC`;
|
|
|
|
// Query without aliases for testing error message
|
|
const queryWithoutAliases = `SELECT count(kubernetes_container_hash), count(kubernetes_container_name), count(kubernetes_host) FROM "${STREAM_NAME}" WHERE kubernetes_namespace_name IS NOT NULL GROUP BY kubernetes_annotations_kubectl_kubernetes_io_default_container`;
|
|
|
|
test.describe("logs testcases", () => {
|
|
test.beforeEach(async ({ page }) => {
|
|
await navigateToBase(page);
|
|
await ingestion(page);
|
|
|
|
// Navigate to logs page
|
|
const logsUrl = `${logData.logsUrl}?org_identifier=${process.env["ORGNAME"] ?? "default"}`;
|
|
|
|
await page.goto(logsUrl);
|
|
await page.waitForLoadState("networkidle", { timeout: 15000 });
|
|
|
|
// Instantiate PageManager with the current page
|
|
const pm = new PageManager(page);
|
|
|
|
await pm.logsVisualise.logsApplyQueryButton();
|
|
});
|
|
|
|
test("should create logs when queries are ingested into the search field", {
|
|
tag: ["@logs", "@visualize", "@P2", "@all"],
|
|
}, async ({ page }) => {
|
|
const pm = new PageManager(page);
|
|
|
|
await pm.logsVisualise.openLogs();
|
|
await pm.logsVisualise.enableSQLMode();
|
|
|
|
await pm.logsVisualise.logsSelectStream("e2e_automate");
|
|
await pm.logsVisualise.setRelative("30", "m");
|
|
await pm.logsVisualise.logsApplyQueryButton();
|
|
|
|
// Verify query executed without errors by waiting for results to load
|
|
await pm.logsVisualise.waitForLogsQueryToComplete();
|
|
await pm.logsVisualise.verifyNoDashboardErrors();
|
|
testLogger.info("Logs query executed successfully after ingestion");
|
|
});
|
|
|
|
test("should handle large datasets and complex SQL queries without showing an error on the chart", {
|
|
tag: ["@logs", "@visualize", "@P1", "@all"],
|
|
}, async ({ page }) => {
|
|
const pm = new PageManager(page);
|
|
|
|
await pm.logsVisualise.openLogs();
|
|
await pm.logsVisualise.openQueryEditor();
|
|
|
|
await pm.logsVisualise.fillLogsQueryEditor(largeDatasetSqlQuery);
|
|
|
|
await pm.logsVisualise.setRelative("30", "m");
|
|
await pm.logsVisualise.logsApplyQueryButton();
|
|
await pm.logsVisualise.openVisualiseTab();
|
|
|
|
// Assert that no dashboard errors are displayed (uses real selector)
|
|
await pm.logsVisualise.verifyNoDashboardErrors();
|
|
testLogger.info("Large dataset query rendered without errors");
|
|
});
|
|
|
|
test("should redirect the correct chart type when added the aggregation query", {
|
|
tag: ["@logs", "@visualize", "@P1", "@all"],
|
|
}, async ({ page }) => {
|
|
const pm = new PageManager(page);
|
|
|
|
await pm.logsVisualise.openLogs();
|
|
|
|
await pm.logsVisualise.fillLogsQueryEditor(largeDatasetSqlQuery);
|
|
|
|
await pm.logsVisualise.setRelative("30", "m");
|
|
|
|
await pm.logsVisualise.logsApplyQueryButton();
|
|
|
|
await pm.logsVisualise.openVisualiseTab();
|
|
|
|
await pm.logsVisualise.verifyChartRenders(page);
|
|
|
|
await pm.logsVisualise.streamIndexList();
|
|
|
|
// Verify table panel is visible with data
|
|
await pm.logsVisualise.verifyChartTypeSelected(page, "table", true);
|
|
await pm.logsVisualise.verifyChartRenders(page);
|
|
testLogger.info("Aggregation query correctly defaulted to table chart");
|
|
});
|
|
|
|
test("should not show dashboard errors when changing chart types with aggregation query", {
|
|
tag: ["@logs", "@visualize", "@P1", "@all"],
|
|
}, async ({ page }) => {
|
|
const pm = new PageManager(page);
|
|
|
|
await pm.logsVisualise.openLogs();
|
|
|
|
await pm.logsVisualise.fillLogsQueryEditor(largeDatasetSqlQuery);
|
|
|
|
await pm.logsVisualise.setRelative("30", "m");
|
|
|
|
await pm.logsVisualise.logsApplyQueryButton();
|
|
|
|
await pm.logsVisualise.openVisualiseTab();
|
|
|
|
await pm.logsVisualise.verifyChartRenders(page);
|
|
|
|
// Define chart types to test
|
|
const chartTypes = ["table", "line", "bar", "area", "scatter", "pie"];
|
|
|
|
// Test each chart type using page object methods
|
|
for (const chartType of chartTypes) {
|
|
await pm.logsVisualise.selectChartType(chartType);
|
|
|
|
// Wait for chart to load
|
|
await pm.logsVisualise.verifyChartRenders(page);
|
|
|
|
// Check for dashboard errors
|
|
const errorResult = await pm.logsVisualise.checkDashboardErrors(
|
|
page,
|
|
chartType
|
|
);
|
|
|
|
if (errorResult.hasErrors) {
|
|
testLogger.error(`Dashboard errors found for ${chartType} chart`, { errors: errorResult.errors });
|
|
expect(errorResult.errorTextCount).toBe(0);
|
|
expect(errorResult.errorListCount).toBe(0);
|
|
}
|
|
|
|
// Verify the chart renders successfully
|
|
const chartRendered = await pm.logsVisualise.verifyChartRenders(page);
|
|
expect(chartRendered).toBe(true);
|
|
testLogger.info(`Chart type "${chartType}" rendered without errors`);
|
|
}
|
|
});
|
|
|
|
test("should set line chart as default when using histogram query", {
|
|
tag: ["@logs", "@visualize", "@P1", "@all"],
|
|
}, async ({ page }) => {
|
|
const pm = new PageManager(page);
|
|
|
|
await pm.logsVisualise.openLogs();
|
|
|
|
await pm.logsVisualise.fillLogsQueryEditor(histogramQuery);
|
|
|
|
await pm.logsVisualise.setRelative("30", "m");
|
|
|
|
await pm.logsVisualise.logsApplyQueryButton();
|
|
|
|
await pm.logsVisualise.openVisualiseTab();
|
|
|
|
await pm.logsVisualise.verifyChartRenders(page);
|
|
|
|
// Verify line chart is selected as default for histogram queries
|
|
await pm.logsVisualise.verifyChartTypeSelected(page, "line", true);
|
|
|
|
// Verify table chart is NOT selected for histogram queries
|
|
await pm.logsVisualise.verifyChartTypeSelected(page, "table", false);
|
|
|
|
// Verify chart canvas renders successfully
|
|
await expect(pm.logsVisualise.getChartRendererCanvas().last()).toBeVisible();
|
|
|
|
// Verify chart renders without errors
|
|
const chartRendered = await pm.logsVisualise.verifyChartRenders(page);
|
|
expect(chartRendered).toBe(true);
|
|
testLogger.info("Histogram query defaulted to line chart as expected");
|
|
});
|
|
|
|
test("Should display the correct query in the dashboard when saved from a Table chart.", {
|
|
tag: ["@logs", "@visualize", "@dashboard", "@P1", "@all"],
|
|
}, async ({ page }) => {
|
|
const pm = new PageManager(page);
|
|
|
|
await pm.logsVisualise.openLogs();
|
|
|
|
await pm.logsVisualise.fillLogsQueryEditor(largeDatasetSqlQuery);
|
|
|
|
await pm.logsVisualise.setRelative("30", "m");
|
|
|
|
await pm.logsVisualise.logsApplyQueryButton();
|
|
|
|
await pm.logsVisualise.openVisualiseTab();
|
|
|
|
await pm.logsVisualise.runQueryAndWaitForCompletion();
|
|
|
|
await pm.logsVisualise.verifyChartRenders(page);
|
|
|
|
await pm.logsVisualise.addPanelToNewDashboard(
|
|
randomDashboardName,
|
|
panelName
|
|
);
|
|
|
|
// Wait for and assert the success message
|
|
await pm.logsVisualise.verifyToastMessage("Panel added to dashboard");
|
|
|
|
await pm.logsVisualise.openPanelQueryInspector(panelName);
|
|
|
|
// Verify the executed query contains the expected SQL
|
|
await pm.logsVisualise.verifyExecutedQueryContains(["e2e_automate", "x_axis_1"]);
|
|
await pm.logsVisualise.closeQueryInspector();
|
|
testLogger.info("Table chart query inspector verified");
|
|
|
|
await pm.logsVisualise.clickDashboardBackBtn();
|
|
await deleteDashboard(page, randomDashboardName);
|
|
});
|
|
|
|
test("should display the correct query in the dashboard when saved from a Line chart.", {
|
|
tag: ["@logs", "@visualize", "@dashboard", "@P1", "@all"],
|
|
}, async ({ page }) => {
|
|
const pm = new PageManager(page);
|
|
|
|
await pm.logsVisualise.openLogs();
|
|
|
|
await pm.logsVisualise.fillLogsQueryEditor(histogramQuery);
|
|
|
|
await pm.logsVisualise.setRelative("30", "m");
|
|
|
|
await pm.logsVisualise.logsApplyQueryButton();
|
|
|
|
await pm.logsVisualise.openVisualiseTab();
|
|
|
|
await pm.logsVisualise.runQueryAndWaitForCompletion();
|
|
|
|
await pm.logsVisualise.addPanelToNewDashboard(
|
|
randomDashboardName,
|
|
panelName
|
|
);
|
|
|
|
// Wait for and assert the success message
|
|
await pm.logsVisualise.verifyToastMessage("Panel added to dashboard");
|
|
|
|
await pm.logsVisualise.openPanelQueryInspector(panelName);
|
|
|
|
// Verify the executed query contains the expected SQL
|
|
await pm.logsVisualise.verifyExecutedQueryContains(["histogram(_timestamp)", "e2e_automate"]);
|
|
await pm.logsVisualise.closeQueryInspector();
|
|
testLogger.info("Line chart query inspector verified");
|
|
|
|
await pm.logsVisualise.clickDashboardBackBtn();
|
|
await deleteDashboard(page, randomDashboardName);
|
|
});
|
|
|
|
test("should save SELECT * query panel to dashboard successfully", {
|
|
tag: ["@logs", "@visualize", "@dashboard", "@P1", "@all"],
|
|
}, async ({ page }) => {
|
|
const pm = new PageManager(page);
|
|
|
|
await pm.logsVisualise.openLogs();
|
|
|
|
const selectAllQuery = `SELECT * FROM "${STREAM_NAME}"`;
|
|
|
|
await pm.logsVisualise.fillLogsQueryEditor(selectAllQuery);
|
|
|
|
await pm.logsVisualise.setRelative("30", "m");
|
|
|
|
await pm.logsVisualise.logsApplyQueryButton();
|
|
|
|
await pm.logsVisualise.openVisualiseTab();
|
|
|
|
await pm.logsVisualise.verifyChartRenders(page);
|
|
|
|
// Run the query in visualize tab before saving to dashboard
|
|
await pm.logsVisualise.runQueryAndWaitForCompletion();
|
|
|
|
await pm.logsVisualise.addPanelToNewDashboard(
|
|
randomDashboardName,
|
|
panelName
|
|
);
|
|
|
|
// Wait for and assert the success message
|
|
await pm.logsVisualise.verifyToastMessage("Panel added to dashboard");
|
|
|
|
// Verify the panel is visible on the dashboard
|
|
const panelDropdown = pm.logsVisualise.getPanelDropdown(panelName);
|
|
await expect(panelDropdown).toBeVisible({ timeout: 20000 });
|
|
testLogger.info("SELECT * panel saved to dashboard successfully");
|
|
|
|
await pm.logsVisualise.clickDashboardBackBtn();
|
|
await deleteDashboard(page, randomDashboardName);
|
|
});
|
|
|
|
test("should show error message when using aggregation functions without aliases in SQL query", {
|
|
tag: ["@logs", "@visualize", "@P2", "@all"],
|
|
}, async ({ page }) => {
|
|
const pm = new PageManager(page);
|
|
|
|
await pm.logsVisualise.openLogs();
|
|
|
|
await pm.logsVisualise.fillLogsQueryEditor(queryWithoutAliases);
|
|
|
|
await pm.logsVisualise.setRelative("30", "m");
|
|
|
|
await pm.logsVisualise.logsApplyQueryButton();
|
|
|
|
await pm.logsVisualise.openVisualiseTab();
|
|
|
|
// Wait for error message to appear in toast
|
|
await pm.logsVisualise.verifyToastMessage("Fields using aggregation functions must have aliases");
|
|
testLogger.info("Alias error message displayed as expected");
|
|
});
|
|
|
|
test("should show quick mode toggle accessible in visualize tab", {
|
|
tag: ["@logs", "@visualize", "@P2", "@all"],
|
|
}, async ({ page }) => {
|
|
const pm = new PageManager(page);
|
|
|
|
await pm.logsVisualise.openLogs();
|
|
|
|
await pm.logsVisualise.logsSelectStream("e2e_automate");
|
|
|
|
await waitForDateTimeButtonToBeEnabled(page);
|
|
|
|
await pm.logsVisualise.setRelative("30", "m");
|
|
|
|
await pm.logsVisualise.logsApplyQueryButton();
|
|
|
|
await pm.logsVisualise.openVisualiseTab();
|
|
|
|
// Read the current quick mode state (varies by environment)
|
|
const initialState = await pm.logsVisualise.getQuickModeToggleState();
|
|
testLogger.info(`Quick mode initial state: ${initialState}`);
|
|
|
|
// Verify we can read the state (boolean, not null/undefined)
|
|
expect(typeof initialState).toBe("boolean");
|
|
testLogger.info("Quick mode toggle is accessible in visualize tab");
|
|
});
|
|
|
|
test("should show connect null values toggle as true by default when visualizing histogram query with HAVING clause", {
|
|
tag: ["@logs", "@visualize", "@P2", "@all"],
|
|
}, async ({ page }) => {
|
|
const pm = new PageManager(page);
|
|
|
|
await pm.logsVisualise.openLogs();
|
|
|
|
await pm.logsVisualise.fillLogsQueryEditor(histogramQueryWithHaving);
|
|
|
|
await pm.logsVisualise.setRelative("30", "m");
|
|
|
|
await pm.logsVisualise.logsApplyQueryButton();
|
|
|
|
await pm.logsVisualise.openVisualiseTab();
|
|
|
|
// Wait for chart to render
|
|
await pm.logsVisualise.verifyChartRenders(page);
|
|
|
|
// Open the config panel to access the connect null values toggle
|
|
const panelConfigs = new DashboardPanelConfigs(page);
|
|
await panelConfigs.openConfigPanel();
|
|
|
|
// Verify connect null values toggle is true by default
|
|
const connectNullState = await panelConfigs.verifyConnectNullValuesToggle(true);
|
|
expect(connectNullState).toBe(true);
|
|
|
|
// Additional assertion using page object locator
|
|
const connectNullToggleBtn = pm.logsVisualise.getConnectNullValuesToggle();
|
|
await expect(connectNullToggleBtn).toHaveAttribute("aria-checked", "true");
|
|
testLogger.info("Connect null values toggle verified as true by default");
|
|
});
|
|
|
|
});
|