test: repair two failing dashboard E2E tests (#12395)

## Summary

- **camelCase zero-value line chart** (`dashboard.spec.js:826`): SQL
referenced `kubernetes_namespace_name` / `kubernetes_container_name` /
`kubernetes_pod_id` with underscores, but `e2e_automate` stores those
fields with dot notation (`kubernetes.namespace_name`). DataFusion
returned "column not found", `errorDetail.message` was set,
`ChartRenderer` was unmounted (behind a `v-if`), and the test timed out
waiting for `[data-test="chart-renderer"]`. Fixed by switching to
`level` / `method` / `stream` — fields guaranteed to exist — with
impossible conditions so all series values are zero.

- **Sankey no-data test** (`dashboard-sankey.spec.js:400`): After
`waitForChartToRender()` there is an async gap between the apply button
re-enabling and Vue committing the "No Data" text to the DOM. The old
`waitForFunction` used `offsetParent !== null`, which fires immediately
for the empty zero-height no-data div (`position: absolute`, no content
→ 0px height). `isVisible()` then returned `false`, producing `Expected
true, Received false`. Fixed by replacing the custom logic with
`expect.poll()` that reads `textContent` — retrying until the text is
actually committed to the DOM.

## Test plan

- [x] Both tests verified passing locally against a hosted enterprise OO
instance (localhost:5080)
- [ ] CI run on `o2-enterprise` PR to confirm `Dashboards-Core` and
`Dashboards-Charts` jobs go green

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Shrinath Rao
2026-06-02 04:49:40 +00:00
committed by GitHub
co-authored by Claude Sonnet 4.6
parent 3e865a3e6a
commit b6093cb996
4 changed files with 29 additions and 28 deletions
@@ -482,10 +482,14 @@ export default class ChartTypeSelector {
async enterCustomSQL(query) {
await this.queryEditor.waitFor({ state: "visible", timeout: 10000 });
await this.queryEditor.click();
// Clear any placeholder/default content, then type the new query
await this.page.keyboard.press("Escape"); // dismiss any open Monaco suggestion
await this.page.keyboard.press("Control+a");
await this.page.keyboard.press("Delete");
await this.page.keyboard.type(query);
// Use insertText (single input event) instead of type (per-character keydown events)
// to prevent Monaco's autocomplete from accepting suggestions mid-input and
// mangling the SQL (e.g. replacing table names with function calls).
await this.page.keyboard.insertText(query);
await this.page.keyboard.press("Escape"); // dismiss any suggestion triggered at end
testLogger.debug('Entered custom SQL query', { query });
}
@@ -60,8 +60,10 @@ export default class DashboardactionPage {
}
// Get dashboard-error locator
// DashboardErrors.vue has two nested elements with the same data-test attribute;
// use .first() to avoid Playwright strict-mode throws on multi-element locators.
getDashboardErrorLocator() {
return this.page.locator('[data-test="dashboard-error"]');
return this.page.locator('[data-test="dashboard-error"]').first();
}
// Generate a unique panel name
@@ -421,29 +421,21 @@ test.describe("Sankey chart testcases", () => {
await pm.dashboardPanelActions.waitForChartToRender();
// Verify no data or error is shown (incomplete Sankey config).
// Both elements may be visible simultaneously on CI, so check each
// independently rather than using .or() which throws strict mode
// violation when both match.
// Use expect.poll to handle the async gap between the apply button
// re-enabling and Vue committing the no-data text to the DOM.
// Checking textContent (not just isVisible) avoids false-positives
// from the empty zero-height no-data div that exists when noData === "".
const noDataLocator = pm.dashboardPanelActions.getNoDataLocator();
const dashErrorLocator = pm.dashboardPanelActions.getDashboardErrorLocator();
const noDataVisible = await noDataLocator.isVisible().catch(() => false);
const dashErrorVisible = await dashErrorLocator.isVisible().catch(() => false);
if (!noDataVisible && !dashErrorVisible) {
// Neither visible yet — wait for either one to appear
await page.waitForFunction(() => {
const noData = document.querySelector('[data-test="no-data"]');
const dashErr = document.querySelector('[data-test="dashboard-error"]');
return (noData && noData.offsetParent !== null) ||
(dashErr && dashErr.offsetParent !== null);
}, { timeout: 10000 });
}
expect(noDataVisible || dashErrorVisible ||
await noDataLocator.isVisible().catch(() => false) ||
await dashErrorLocator.isVisible().catch(() => false)
).toBe(true);
await expect.poll(async () => {
const noDataText = (await noDataLocator.textContent().catch(() => '')).trim();
const dashErrorVisible = await dashErrorLocator.isVisible().catch(() => false);
return noDataText.length > 0 || dashErrorVisible;
}, {
timeout: 12000,
message: 'Expected "No Data" or dashboard error when only Source field is configured',
}).toBeTruthy();
testLogger.info("Sankey no data state verified");
@@ -841,13 +841,16 @@ test.describe("dashboard UI testcases", () => {
await pm.chartTypeSelector.removeField("x_axis_1", "x");
await pm.chartTypeSelector.selectChartType("line");
// Set custom SQL — fields are populated client-side by the SQL parser
// Set custom SQL — fields are populated client-side by the SQL parser.
// Use `level` and `method` (guaranteed fields in e2e_automate) with
// impossible conditions so all counts are zero. This validates that the
// line chart renders zero-valued camelCase-aliased series without errors.
await pm.chartTypeSelector.setCustomSQL(
`SELECT histogram(_timestamp, '5 minute') AS "_time",
COUNT(CASE WHEN kubernetes_namespace_name = 'ziox' AND kubernetes_container_name LIKE '4%' THEN 1 END) AS "4xxErrorCount",
COUNT(CASE WHEN kubernetes_namespace_name = 'ziox' AND kubernetes_container_name LIKE 'tes%' THEN 1 END) AS "5xxErrorCount",
COUNT(CASE WHEN kubernetes_namespace_name = 'ziox' AND kubernetes_pod_id IS NULL THEN 1 END) AS "NullErrorCount",
COUNT(CASE WHEN kubernetes_container_name = 'prometheus' THEN 1 END) AS "pageViewCount"
COUNT(CASE WHEN level = 'nonexistentLevel_abc' THEN 1 END) AS "4xxErrorCount",
COUNT(CASE WHEN level = 'nonexistentLevel_def' THEN 1 END) AS "5xxErrorCount",
COUNT(CASE WHEN method = 'NONEXISTENT_METHOD_xyz' THEN 1 END) AS "NullErrorCount",
COUNT(CASE WHEN stream = 'nonexistentStream_xyz' THEN 1 END) AS "pageViewCount"
FROM e2e_automate
GROUP BY _time
ORDER BY _time ASC`