The open and composable observability and data visualization platform. Visualize metrics, logs, and traces from multiple sources like Prometheus, Loki, Elasticsearch, InfluxDB, Postgres and many more.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 
grafana/e2e-playwright/panels-suite/table-kitchenSink.spec.ts

331 lines
15 KiB

import { Page, Locator } from '@playwright/test';
import { test, expect } from '@grafana/plugin-e2e';
test.use({
viewport: { width: 1600, height: 1080 },
featureToggles: {
tableNextGen: true,
},
});
// helper utils
const getCell = async (loc: Page | Locator, rowIdx: number, colIdx: number) =>
loc
.getByRole('row')
.nth(rowIdx)
.getByRole(rowIdx === 0 ? 'columnheader' : 'gridcell')
.nth(colIdx);
const getCellHeight = async (loc: Page | Locator, rowIdx: number, colIdx: number) => {
const cell = await getCell(loc, rowIdx, colIdx);
return (await cell.boundingBox())?.height ?? 0;
};
test.describe(
'Panels test: Table - Kitchen Sink',
{
tag: ['@panels'],
},
() => {
test('Tests word wrap, hover overflow, and cell inspect', async ({ gotoDashboardPage, selectors, page }) => {
const dashboardPage = await gotoDashboardPage({
uid: 'dcb9f5e9-8066-4397-889e-864b99555dbb',
queryParams: new URLSearchParams({ editPanel: '1' }),
});
await expect(
dashboardPage.getByGrafanaSelector(selectors.components.Panels.Panel.title('Table - Kitchen Sink'))
).toBeVisible();
// text wrapping is enabled by default on this panel.
await expect(getCellHeight(page, 1, 5)).resolves.toBeGreaterThan(100);
// toggle the lorem ipsum column's wrap text toggle and confirm that the height shrinks.
await dashboardPage
.getByGrafanaSelector(selectors.components.PanelEditor.OptionsPane.fieldLabel('Wrap text'))
.last()
.click();
await expect(getCellHeight(page, 1, 5)).resolves.toBeLessThan(100);
// test that hover overflow works.
const loremIpsumCell = await getCell(page, 1, 5);
await loremIpsumCell.hover();
await expect(getCellHeight(page, 1, 5)).resolves.toBeGreaterThan(100);
await (await getCell(page, 1, 6)).hover();
await expect(getCellHeight(page, 1, 5)).resolves.toBeLessThan(100);
// enable cell inspect, confirm that hover no longer triggers.
await dashboardPage
.getByGrafanaSelector(
selectors.components.PanelEditor.OptionsPane.fieldLabel('Cell options Cell value inspect')
)
.first()
.locator('label[for="custom.inspect"]')
.click();
await loremIpsumCell.hover();
await expect(getCellHeight(page, 1, 5)).resolves.toBeLessThan(100);
// click cell inspect, check that cell inspection pops open in the side as we'd expect.
await loremIpsumCell.getByLabel('Inspect value').click();
const loremIpsumText = await loremIpsumCell.textContent();
expect(loremIpsumText).toBeDefined();
await expect(page.getByRole('dialog').getByText(loremIpsumText!)).toBeVisible();
});
test('Tests visibility and display name via overrides', async ({ gotoDashboardPage, selectors, page }) => {
const dashboardPage = await gotoDashboardPage({
uid: 'dcb9f5e9-8066-4397-889e-864b99555dbb',
queryParams: new URLSearchParams({ editPanel: '1' }),
});
await expect(
dashboardPage.getByGrafanaSelector(selectors.components.Panels.Panel.title('Table - Kitchen Sink'))
).toBeVisible();
// confirm that "State" column is hidden by default.
expect(page.getByRole('row').nth(0)).not.toContainText('State');
// toggle the "State" column visibility and test that it appears before re-hiding it.
// FIXME this selector is utterly godawful, but there's no way to give testIds or aria-labels or anything to
// the panel editor builder. we should fix that to make e2e's easier to write for our team.
const hideStateColumnSwitch = page.locator('[id="Override 12"]').locator('label').last();
await hideStateColumnSwitch.click();
expect(page.getByRole('row').nth(0)).toContainText('State');
// now change the display name of the "State" column.
// FIXME it would be good to have a better selector here too.
const displayNameInput = page.locator('[id="Override 12"]').locator('input[value="State"]').last();
await displayNameInput.fill('State (renamed)');
await displayNameInput.press('Enter');
expect(page.getByRole('row').nth(0)).toContainText('State (renamed)');
});
// we test niche cases for sorting, filtering, pagination, etc. in a unit tests already.
// we mainly want to test the happiest paths for these in e2es as well to check for integration
// issues, but the unit tests can confirm that the internal logic works as expected much more quickly and thoroughly.
// hashtag testing pyramid.
test('Tests sorting by column', async ({ gotoDashboardPage, selectors, page }) => {
const dashboardPage = await gotoDashboardPage({
uid: 'dcb9f5e9-8066-4397-889e-864b99555dbb',
queryParams: new URLSearchParams({ editPanel: '1' }),
});
await expect(
dashboardPage.getByGrafanaSelector(selectors.components.Panels.Panel.title('Table - Kitchen Sink'))
).toBeVisible();
// click the "State" column header to sort it.
const stateColumnHeader = await 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 stateColumnHeader.getByText('Info').click();
await expect(stateColumnHeader).toHaveAttribute('aria-sort', 'descending');
expect(getCell(page, 1, 1)).resolves.toContainText('up'); // up or up fast
await stateColumnHeader.getByText('Info').click();
await expect(stateColumnHeader).not.toHaveAttribute('aria-sort');
});
test('Tests filtering within a column', async ({ gotoDashboardPage, selectors, page }) => {
const dashboardPage = await gotoDashboardPage({
uid: 'dcb9f5e9-8066-4397-889e-864b99555dbb',
queryParams: new URLSearchParams({ editPanel: '1' }),
});
await expect(
dashboardPage.getByGrafanaSelector(selectors.components.Panels.Panel.title('Table - Kitchen Sink'))
).toBeVisible();
const stateColumnHeader = page.getByRole('columnheader').filter({ hasText: 'Info' });
// get the first value in the "State" column, filter it out, then check that it went away.
const firstStateValue = (await (await getCell(page, 1, 1)).textContent())!;
await stateColumnHeader
.getByTestId(selectors.components.Panels.Visualization.TableNG.Filters.HeaderButton)
.click();
const filterContainer = dashboardPage.getByGrafanaSelector(
selectors.components.Panels.Visualization.TableNG.Filters.Container
);
await expect(filterContainer).toBeVisible();
// select all, then click the first value to unselect it, filtering it out.
await filterContainer.getByTestId(selectors.components.Panels.Visualization.TableNG.Filters.SelectAll).click();
await filterContainer.getByTitle(firstStateValue, { exact: true }).locator('label').click();
await filterContainer.getByRole('button', { name: 'Ok' }).click();
// make sure the filter container closed when we clicked "Ok".
await expect(filterContainer).not.toBeVisible();
// did it actually filter out our value?
await expect(getCell(page, 1, 1)).resolves.not.toHaveText(firstStateValue);
});
test('Tests pagination, row height adjustment', async ({ gotoDashboardPage, selectors, page }) => {
const rowRe = /([\d]+) - ([\d]+) of ([\d]+) rows/;
const getRowStatus = async (page: Page | Locator) => {
const text = (await page.getByText(rowRe).textContent()) ?? '';
const match = text.match(rowRe);
return {
start: parseInt(match?.[1] ?? '0', 10),
end: parseInt(match?.[2] ?? '0', 10),
total: parseInt(match?.[3] ?? '0', 10),
};
};
const dashboardPage = await gotoDashboardPage({
uid: 'dcb9f5e9-8066-4397-889e-864b99555dbb',
queryParams: new URLSearchParams({ editPanel: '1' }),
});
await expect(
dashboardPage.getByGrafanaSelector(selectors.components.Panels.Panel.title('Table - Kitchen Sink'))
).toBeVisible();
await page
.getByLabel(selectors.components.PanelEditor.OptionsPane.fieldLabel(`Enable pagination`), { exact: true })
.click();
// because of text wrapping, we're guaranteed to only be showing a single row when we enable pagination.
await expect(page.getByText(/([\d]+) - ([\d]+) of ([\d]+) rows/)).toBeVisible();
// disable text wrap and see the number of rows.
await dashboardPage
.getByGrafanaSelector(selectors.components.PanelEditor.OptionsPane.fieldLabel('Wrap text'))
.last()
.click();
// any number of rows that is not "1" is allowed here, we don't want to police the exact number of rows that
// are rendered since there are tons of factors which could effect this. we do want to grab this number for comparison
// in a second, though.
const smallRowStatus = await getRowStatus(page);
expect(smallRowStatus.end).toBeGreaterThan(1);
expect(page.getByRole('grid').getByRole('row')).toHaveCount(smallRowStatus.end + 1);
// change cell height to Large
await dashboardPage
.getByGrafanaSelector(selectors.components.PanelEditor.OptionsPane.fieldLabel('Table Cell height'))
.locator('input')
.last()
.click();
const largeRowStatus = await getRowStatus(page);
expect(largeRowStatus.end).toBeLessThan(smallRowStatus.end);
expect(page.getByRole('grid').getByRole('row')).toHaveCount(largeRowStatus.end + 1);
// click a page over with the directional nav
await page.getByLabel('next page').click();
const nextPageStatus = await getRowStatus(page);
expect(nextPageStatus.start).toBe(largeRowStatus.end + 1);
expect(nextPageStatus.end).toBe(largeRowStatus.end * 2);
expect(nextPageStatus.total).toBe(largeRowStatus.total);
// click a page number
await page
.getByTestId('data-testid panel content')
.getByRole('navigation')
.getByText('4', { exact: true })
.click();
const fourthPageStatus = await getRowStatus(page);
expect(fourthPageStatus.start).toBe(largeRowStatus.end * 3 + 1);
expect(fourthPageStatus.end).toBe(largeRowStatus.end * 4);
expect(fourthPageStatus.total).toBe(largeRowStatus.total);
});
test('Tests DataLinks (single and multi) and actions', async ({ gotoDashboardPage, selectors, page }) => {
const addDataLink = async (title: string, url: string) => {
await dashboardPage
.getByGrafanaSelector(
selectors.components.PanelEditor.OptionsPane.fieldLabel('Data links and actions Data links')
)
.locator('button')
.filter({ hasText: 'Add link' })
.click();
// DataLinks dialog has popped open - fill it in and add a global datalink.
await expect(page.getByRole('dialog')).toBeVisible();
await page.getByRole('dialog').locator('#link-title').fill(title);
await page.getByRole('dialog').locator('#data-link-input [contenteditable="true"]').focus();
await page.getByRole('dialog').locator('#data-link-input [contenteditable="true"]').fill(url);
await page.getByRole('dialog').locator('#data-link-input [contenteditable="true"]').blur();
await page.getByRole('dialog').getByRole('button', { name: 'Save' }).click();
await expect(page.getByRole('dialog')).not.toBeVisible();
};
const dashboardPage = await gotoDashboardPage({
uid: 'dcb9f5e9-8066-4397-889e-864b99555dbb',
queryParams: new URLSearchParams({ editPanel: '1' }),
});
await expect(
dashboardPage.getByGrafanaSelector(selectors.components.Panels.Panel.title('Table - Kitchen Sink'))
).toBeVisible();
// disable text wrapping for this test to make it easier to click the links, the long lorem ipsum
// can push the links off the screen.
await dashboardPage
.getByGrafanaSelector(selectors.components.PanelEditor.OptionsPane.fieldLabel('Wrap text'))
.last()
.click();
// Info column has a single DataLink by default.
const infoCell = await getCell(page, 1, 1);
await expect(infoCell.locator('a')).toBeVisible();
expect(infoCell.locator('a')).toHaveAttribute('href');
expect(infoCell.locator('a')).not.toHaveAttribute('aria-haspopup');
// now, add a DataLink to the whole table
await addDataLink('Test link', 'https://grafana.com');
// add a DataLink to the whole table, all cells will now have a single link.
const colCount = await page.getByRole('row').nth(1).getByRole('gridcell').count();
for (let colIdx = 0; colIdx < colCount; colIdx++) {
const cell = await 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');
}
const headerContainer = dashboardPage.getByGrafanaSelector(selectors.components.Panels.Panel.headerContainer);
// add another data link. now we'll check that the multi-link popups work.
await addDataLink('Another test link', 'https://grafana.com/foo');
// 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);
if (colIdx === 1) {
// the Info column should still have its single link.
expect(cell.locator('a')).not.toHaveAttribute('aria-haspopup', 'menu');
continue;
}
await cell.locator('a').click({ force: true });
await expect(page.getByTestId(selectors.components.DataLinksActionsTooltip.tooltipWrapper)).toBeVisible();
await headerContainer.click(); // convenient just to click the header to close the tooltip.
await expect(page.getByTestId(selectors.components.DataLinksActionsTooltip.tooltipWrapper)).not.toBeVisible();
}
// add an Action to the whole table and check that the action button is added to the tooltip.
// TODO -- saving for another day.
});
test('Empty Table panel', async ({ gotoDashboardPage, selectors }) => {
const dashboardPage = await gotoDashboardPage({
uid: 'dcb9f5e9-8066-4397-889e-864b99555dbb',
queryParams: new URLSearchParams({ editPanel: '2' }),
});
await expect(
dashboardPage.getByGrafanaSelector(selectors.components.Panels.Panel.PanelDataErrorMessage)
).toBeVisible();
await expect(
dashboardPage.getByGrafanaSelector(selectors.components.Panels.Panel.title('Table - Kitchen Sink'))
).not.toBeVisible();
});
}
);