E2E: Improve ad-hoc filtering test (#113558)
* E2E: Improve ad-hoc filtering test * remove unused import * fix some table e2es after making getCell sync
This commit is contained in:
@@ -1,16 +1,9 @@
|
||||
import { Page, Locator } from '@playwright/test';
|
||||
|
||||
import { test, expect } from '@grafana/plugin-e2e';
|
||||
|
||||
import testDashboard from '../dashboards/AdHocFilterTest.json';
|
||||
import { getCell } from '../panels-suite/table-utils';
|
||||
|
||||
// Helper function to get a specific cell in a table
|
||||
const getCell = async (loc: Page | Locator, rowIdx: number, colIdx: number) =>
|
||||
loc
|
||||
.getByRole('row')
|
||||
.nth(rowIdx)
|
||||
.getByRole(rowIdx === 0 ? 'columnheader' : 'gridcell')
|
||||
.nth(colIdx);
|
||||
const fixture = require('../fixtures/prometheus-response.json');
|
||||
|
||||
test.describe(
|
||||
'Dashboard with Table powered by Prometheus data source',
|
||||
@@ -46,80 +39,90 @@ test.describe(
|
||||
gotoDashboardPage,
|
||||
selectors,
|
||||
}) => {
|
||||
// Handle query and query_range API calls
|
||||
// Handle query and query_range API calls. Ideally, this would instead be directly tested against gdev-prometheus.
|
||||
await page.route(/\/api\/ds\/query/, async (route) => {
|
||||
const fixture = require('../fixtures/prometheus-response.json');
|
||||
// during the test, we select the "inner_eval" slice to filter; this simulates the behavior
|
||||
// of prometheus applying that filter and removing dataframes from the response.
|
||||
if (route.request().postData()?.includes('{slice=\\\"inner_eval\\\"}')) {
|
||||
fixture.results.A.frames.splice(1, 1);
|
||||
const response = JSON.parse(JSON.stringify(fixture));
|
||||
|
||||
// This simulates the behavior of prometheus applying a filter and removing dataframes from the response where
|
||||
// the label matches the selected filter. We check for either the slice being applied inline into the prometheus
|
||||
// query or the adhoc filter being present in the request body of prometheus applying that filter and removing
|
||||
// dataframes from the response.
|
||||
const postData = route.request().postData();
|
||||
const match =
|
||||
postData?.match(/{slice=\\\"([\w_]+)\\\"}/) ??
|
||||
postData?.match(/"adhocFilters":\[{"key":"slice","operator":"equals","value":"([\w_]+)"}\]/);
|
||||
if (match) {
|
||||
response.results.A.frames = response.results.A.frames.filter((frame) =>
|
||||
frame.schema.fields.every((field) => !field.labels || field.labels.slice === match[1])
|
||||
);
|
||||
}
|
||||
|
||||
await route.fulfill({
|
||||
status: 200,
|
||||
contentType: 'application/json',
|
||||
body: JSON.stringify(fixture),
|
||||
body: JSON.stringify(response),
|
||||
});
|
||||
});
|
||||
|
||||
const dashboardPage = await gotoDashboardPage({ uid: dashboardUID });
|
||||
|
||||
const panel = dashboardPage.getByGrafanaSelector(
|
||||
let panel = dashboardPage.getByGrafanaSelector(
|
||||
selectors.components.Panels.Panel.title('Table powered by Prometheus')
|
||||
);
|
||||
await expect(panel).toBeVisible();
|
||||
await expect(panel, 'panel is rendered').toBeVisible();
|
||||
|
||||
// Wait for the table to load completely
|
||||
await expect(panel.locator('.rdg')).toBeVisible();
|
||||
const table = panel.locator('.rdg');
|
||||
await expect(table, 'table is rendered').toBeVisible();
|
||||
|
||||
// Get the first data cell in the third column (row 1, column 2)
|
||||
const labelValueCell = await getCell(panel, 1, 1);
|
||||
await expect(labelValueCell).toBeVisible();
|
||||
const firstValue = (await getCell(table, 1, 1).textContent())!;
|
||||
const secondValue = (await getCell(table, 2, 1).textContent())!;
|
||||
expect(firstValue, `first cell is "${firstValue}"`).toBeTruthy();
|
||||
expect(secondValue, `second cell is "${secondValue}"`).toBeTruthy();
|
||||
expect(firstValue, 'first and second cell values are different').not.toBe(secondValue);
|
||||
|
||||
// Get the cell value before clicking the filter button
|
||||
const labelValue = await labelValueCell.textContent();
|
||||
expect(labelValue).toBeTruthy();
|
||||
async function performTest(labelValue: string) {
|
||||
// Confirm both cells are rendered before we proceed
|
||||
const otherValue = labelValue === firstValue ? secondValue : firstValue;
|
||||
await expect(table.getByText(labelValue), `"${labelValue}" is rendered`).toContainText(labelValue);
|
||||
await expect(table.getByText(otherValue), `"${otherValue}" is rendered`).toContainText(otherValue);
|
||||
|
||||
const otherValueCell = await getCell(panel, 2, 1);
|
||||
const otherValueLabel = await otherValueCell.textContent();
|
||||
expect(otherValueLabel).toBeTruthy();
|
||||
expect(otherValueLabel).not.toBe(labelValue);
|
||||
// click the "Filter for value" button on the cell with the specified labelValue
|
||||
await table.getByText(labelValue).hover();
|
||||
table.getByText(labelValue).getByRole('button', { name: 'Filter for value' }).click();
|
||||
|
||||
// Hover over the first cell to trigger the appearance of filter actions
|
||||
await labelValueCell.hover();
|
||||
// Look for submenu items that contain the filtered value
|
||||
// The adhoc filter should appear as a filter chip or within the variable controls
|
||||
const submenuItems = dashboardPage.getByGrafanaSelector(selectors.pages.Dashboard.SubMenu.submenuItem);
|
||||
await expect(submenuItems.filter({ hasText: labelValue }), `submenu contains "${labelValue}"`).toBeVisible();
|
||||
await expect(
|
||||
submenuItems.filter({ hasText: otherValue }),
|
||||
`submenu does not contain "${otherValue}"`
|
||||
).toBeHidden();
|
||||
|
||||
// Check if the "Filter for value" button appears on hover
|
||||
const filterForValueButton = labelValueCell.getByRole('button', { name: 'Filter for value' });
|
||||
await expect(filterForValueButton).toBeVisible();
|
||||
// The URL parameter should contain the filter in format like: var-PromAdHoc=["columnName","=","value"]
|
||||
const currentUrl = page.url();
|
||||
const urlParams = new URLSearchParams(new URL(currentUrl).search);
|
||||
const promAdHocParam = urlParams.get('var-PromAdHoc');
|
||||
expect(promAdHocParam, `url contains "${labelValue}"`).toContain(labelValue);
|
||||
expect(promAdHocParam, `url does not contain "${otherValue}"`).not.toContain(otherValue);
|
||||
|
||||
// Click on the "Filter for value" button
|
||||
await filterForValueButton.click();
|
||||
// finally, let's check that the table was updated and that the value was filtered out when the query was re-run
|
||||
await expect(table.getByText(labelValue), `"${labelValue}" is still visible`).toHaveText(labelValue);
|
||||
await expect(table.getByText(otherValue), `"${otherValue}" is filtered out`).toBeHidden();
|
||||
|
||||
// Check if the adhoc filter appears in the dashboard submenu
|
||||
const submenuItems = dashboardPage.getByGrafanaSelector(selectors.pages.Dashboard.SubMenu.submenuItem);
|
||||
await expect(submenuItems.first()).toBeVisible();
|
||||
// Remove the adhoc filter by clicking the submenu item again
|
||||
const filterChip = submenuItems.filter({ hasText: labelValue });
|
||||
await filterChip.getByLabel(/Remove filter with key/).click();
|
||||
await page.click('body', { position: { x: 0, y: 0 } }); // click outside to close the open menu from ad-hoc filters
|
||||
|
||||
// Look for submenu items that contain the filtered value
|
||||
// The adhoc filter should appear as a filter chip or within the variable controls
|
||||
const hasFilterValue = await submenuItems.filter({ hasText: labelValue! }).count();
|
||||
expect(hasFilterValue).toBeGreaterThan(0);
|
||||
// the "first" and "second" cells locators don't work here for some reason.
|
||||
await expect(table.getByText(labelValue), `"${labelValue}" is still rendered`).toContainText(labelValue);
|
||||
await expect(table.getByText(otherValue), `"${otherValue}" is rendered again`).toContainText(otherValue);
|
||||
}
|
||||
|
||||
const hasOtherValue = await submenuItems.filter({ hasText: otherValueLabel! }).count();
|
||||
expect(hasOtherValue).toBe(0);
|
||||
|
||||
// Check if the URL contains the var-PromAdHoc parameter with the filtered value
|
||||
const currentUrl = page.url();
|
||||
expect(currentUrl).toContain('var-PromAdHoc');
|
||||
|
||||
// The URL parameter should contain the filter in format like: var-PromAdHoc=["columnName","=","value"]
|
||||
const urlParams = new URLSearchParams(new URL(currentUrl).search);
|
||||
const promAdHocParam = urlParams.get('var-PromAdHoc');
|
||||
expect(promAdHocParam).toBeTruthy();
|
||||
expect(promAdHocParam).toContain(labelValue!);
|
||||
expect(promAdHocParam).not.toContain(otherValueLabel!);
|
||||
|
||||
// finally, let's check that the table was updated and that the value was filtered out when the query was re-run
|
||||
await expect(otherValueCell).toBeHidden();
|
||||
await performTest(firstValue);
|
||||
await performTest(secondValue);
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
@@ -65,11 +65,11 @@ test.describe('Panels test: Table - Kitchen Sink', { tag: ['@panels', '@table']
|
||||
await expect(getCellHeight(page, 1, longTextColIdx)).resolves.toBeLessThan(100);
|
||||
|
||||
// test that hover overflow works.
|
||||
const loremIpsumCell = await getCell(page, 1, longTextColIdx);
|
||||
const loremIpsumCell = getCell(page, 1, longTextColIdx);
|
||||
await loremIpsumCell.scrollIntoViewIfNeeded();
|
||||
await loremIpsumCell.hover();
|
||||
await expect(getCellHeight(page, 1, longTextColIdx)).resolves.toBeGreaterThan(100);
|
||||
await (await getCell(page, 1, longTextColIdx + 1)).hover();
|
||||
await getCell(page, 1, longTextColIdx + 1).hover();
|
||||
await expect(getCellHeight(page, 1, longTextColIdx)).resolves.toBeLessThan(100);
|
||||
|
||||
// enable cell inspect, confirm that hover no longer triggers.
|
||||
@@ -140,15 +140,15 @@ test.describe('Panels test: Table - Kitchen Sink', { tag: ['@panels', '@table']
|
||||
).toBeVisible();
|
||||
|
||||
// click the "State" column header to sort it.
|
||||
const stateColumnHeader = await getCell(page, 0, 1);
|
||||
const stateColumnHeader = getCell(page, 0, 1);
|
||||
|
||||
await stateColumnHeader.getByText('Info').click();
|
||||
await expect(stateColumnHeader).toHaveAttribute('aria-sort', 'ascending');
|
||||
expect(getCell(page, 1, 1)).resolves.toContainText('down'); // down or down fast
|
||||
await expect(getCell(page, 1, 1)).toContainText('down'); // down or down fast
|
||||
|
||||
await stateColumnHeader.getByText('Info').click();
|
||||
await expect(stateColumnHeader).toHaveAttribute('aria-sort', 'descending');
|
||||
expect(getCell(page, 1, 1)).resolves.toContainText('up'); // up or up fast
|
||||
await expect(getCell(page, 1, 1)).toContainText('up'); // up or up fast
|
||||
|
||||
await stateColumnHeader.getByText('Info').click();
|
||||
await expect(stateColumnHeader).not.toHaveAttribute('aria-sort');
|
||||
@@ -171,7 +171,7 @@ test.describe('Panels test: Table - Kitchen Sink', { tag: ['@panels', '@table']
|
||||
const stateColumnHeader = page.getByRole('columnheader').nth(infoColumnIdx);
|
||||
|
||||
// get the first value in the "State" column, filter it out, then check that it went away.
|
||||
const firstStateValue = (await (await getCell(page, 1, infoColumnIdx)).textContent())!;
|
||||
const firstStateValue = (await getCell(page, 1, infoColumnIdx).textContent())!;
|
||||
await stateColumnHeader.getByTestId(selectors.components.Panels.Visualization.TableNG.Filters.HeaderButton).click();
|
||||
const filterContainer = dashboardPage.getByGrafanaSelector(
|
||||
selectors.components.Panels.Visualization.TableNG.Filters.Container
|
||||
@@ -188,7 +188,7 @@ test.describe('Panels test: Table - Kitchen Sink', { tag: ['@panels', '@table']
|
||||
await expect(filterContainer).not.toBeVisible();
|
||||
|
||||
// did it actually filter out our value?
|
||||
await expect(getCell(page, 1, infoColumnIdx)).resolves.not.toHaveText(firstStateValue);
|
||||
await expect(getCell(page, 1, infoColumnIdx)).not.toHaveText(firstStateValue);
|
||||
});
|
||||
|
||||
test('Tests pagination, row height adjustment', async ({ gotoDashboardPage, selectors, page }) => {
|
||||
@@ -289,7 +289,7 @@ test.describe('Panels test: Table - Kitchen Sink', { tag: ['@panels', '@table']
|
||||
const dataLinkColIdx = await getColumnIdx(page, 'Data Link');
|
||||
|
||||
// Info column has a single DataLink by default.
|
||||
const infoCell = await getCell(page, 1, infoColumnIdx);
|
||||
const infoCell = getCell(page, 1, infoColumnIdx);
|
||||
await expect(infoCell.locator('a')).toBeVisible();
|
||||
expect(infoCell.locator('a')).toHaveAttribute('href');
|
||||
expect(infoCell.locator('a')).not.toHaveAttribute('aria-haspopup');
|
||||
@@ -306,7 +306,7 @@ test.describe('Panels test: Table - Kitchen Sink', { tag: ['@panels', '@table']
|
||||
continue;
|
||||
}
|
||||
|
||||
const cell = await getCell(page, 1, colIdx);
|
||||
const cell = getCell(page, 1, colIdx);
|
||||
await expect(cell.locator('a')).toBeVisible();
|
||||
expect(cell.locator('a')).toHaveAttribute('href');
|
||||
expect(cell.locator('a')).not.toHaveAttribute('aria-haspopup', 'menu');
|
||||
@@ -319,7 +319,7 @@ test.describe('Panels test: Table - Kitchen Sink', { tag: ['@panels', '@table']
|
||||
|
||||
// loop thru the columns, click the links, observe that the tooltip appears, and close the tooltip.
|
||||
for (let colIdx = 0; colIdx < colCount; colIdx++) {
|
||||
const cell = await getCell(page, 1, colIdx);
|
||||
const cell = getCell(page, 1, colIdx);
|
||||
if (colIdx === infoColumnIdx) {
|
||||
// the Info column should still have its single link.
|
||||
expect(cell.locator('a')).not.toHaveAttribute('aria-haspopup', 'menu');
|
||||
@@ -433,7 +433,7 @@ test.describe('Panels test: Table - Kitchen Sink', { tag: ['@panels', '@table']
|
||||
await filterContainer.getByTitle('up', { exact: true }).locator('label').click();
|
||||
await filterContainer.getByRole('button', { name: 'Ok' }).click();
|
||||
|
||||
const cell = await getCell(page, 1, dataLinkColumnIdx);
|
||||
const cell = getCell(page, 1, dataLinkColumnIdx);
|
||||
await expect(cell).toBeVisible();
|
||||
await expect(cell).toHaveCSS('text-decoration', /line-through/);
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Page, Locator } from '@playwright/test';
|
||||
|
||||
export const getCell = async (loc: Page | Locator, rowIdx: number, colIdx: number) =>
|
||||
export const getCell = (loc: Page | Locator, rowIdx: number, colIdx: number) =>
|
||||
loc
|
||||
.getByRole('row')
|
||||
.nth(rowIdx)
|
||||
@@ -8,7 +8,7 @@ export const getCell = async (loc: Page | Locator, rowIdx: number, colIdx: numbe
|
||||
.nth(colIdx);
|
||||
|
||||
export const getCellHeight = async (loc: Page | Locator, rowIdx: number, colIdx: number) => {
|
||||
const cell = await getCell(loc, rowIdx, colIdx);
|
||||
const cell = getCell(loc, rowIdx, colIdx);
|
||||
return (await cell.boundingBox())?.height ?? 0;
|
||||
};
|
||||
|
||||
@@ -18,7 +18,7 @@ export const getColumnIdx = async (loc: Page | Locator, columnName: string) => {
|
||||
let result = -1;
|
||||
const colCount = await loc.getByRole('columnheader').count();
|
||||
for (let colIdx = 0; colIdx < colCount; colIdx++) {
|
||||
const cell = await getCell(loc, 0, colIdx);
|
||||
const cell = getCell(loc, 0, colIdx);
|
||||
if ((await cell.textContent()) === columnName) {
|
||||
result = colIdx;
|
||||
break;
|
||||
|
||||
Reference in New Issue
Block a user