Nested folders: Add e2e test for new browse page (#72590)

* scaffold nested folder structure

* update structure slightly

* update comments

* Added basic scafold for Cypress test

* fix selector name, add table body selector, improve expand aria label

* skip test for now

* undo changes to cypress custom.ini

---------

Co-authored-by: Roxana Turc <anamaria-roxana.turc@grafana.com>
This commit is contained in:
Ashley Harrison
2023-08-02 11:28:13 +01:00
committed by GitHub
parent 922dd94997
commit 6194d8fd8b
9 changed files with 231 additions and 24 deletions

View File

@@ -0,0 +1,147 @@
import { e2e } from '@grafana/e2e';
import { selectors } from '@grafana/e2e-selectors';
import { makeNewDashboardRequestBody } from './utils/makeDashboard';
const NUM_ROOT_FOLDERS = 60;
const NUM_ROOT_DASHBOARDS = 60;
const NUM_NESTED_FOLDERS = 60;
const NUM_NESTED_DASHBOARDS = 60;
// TODO enable this test when nested folders goes live
describe.skip('Dashboard browse (nested)', () => {
const dashboardUIDsToCleanUp: string[] = [];
const folderUIDsToCleanUp: string[] = [];
// Add nested folder structure
before(() => {
e2e.flows.login('admin', 'admin');
// Add root folders
for (let i = 0; i < NUM_ROOT_FOLDERS; i++) {
e2e()
.request({
method: 'POST',
url: '/api/folders',
body: {
title: `Root folder ${i.toString().padStart(2, '0')}`,
},
headers: {
'Content-Type': 'application/json',
},
})
.then((response) => {
folderUIDsToCleanUp.push(response.body.uid);
});
}
// Add root dashboards
for (let i = 0; i < NUM_ROOT_DASHBOARDS; i++) {
e2e()
.request({
method: 'POST',
url: '/api/dashboards/db',
body: makeNewDashboardRequestBody(`Root dashboard ${i.toString().padStart(2, '0')}`),
headers: {
'Content-Type': 'application/json',
},
})
.then((response) => {
dashboardUIDsToCleanUp.push(response.body.uid);
});
}
// Add folder with children
e2e()
.request({
method: 'POST',
url: '/api/folders',
body: {
title: 'A root folder with children',
},
headers: {
'Content-Type': 'application/json',
},
})
.then((response) => {
const folderUid = response.body.uid;
folderUIDsToCleanUp.push(folderUid);
// Add nested folders
for (let i = 0; i < NUM_NESTED_FOLDERS; i++) {
e2e().request({
method: 'POST',
url: '/api/folders',
body: {
title: `Nested folder ${i.toString().padStart(2, '0')}`,
parentUid: folderUid,
},
headers: {
'Content-Type': 'application/json',
},
});
}
// Add nested dashboards
for (let i = 0; i < NUM_NESTED_DASHBOARDS; i++) {
e2e().request({
method: 'POST',
url: '/api/dashboards/db',
body: makeNewDashboardRequestBody(`Nested dashboard ${i.toString().padStart(2, '0')}`, folderUid),
headers: {
'Content-Type': 'application/json',
},
});
}
});
});
// Remove nested folder structure
after(() => {
// Clean up root dashboards
for (const dashboardUID of dashboardUIDsToCleanUp) {
e2e.flows.deleteDashboard({
uid: dashboardUID,
quick: true,
title: '',
});
}
// Clean up root folders (cascading delete will remove any nested folders and dashboards)
for (const folderUID of folderUIDsToCleanUp) {
e2e().request({
method: 'DELETE',
url: `/api/folders/${folderUID}`,
qs: {
forceDeleteRules: false,
},
});
}
});
it('pagination works correctly for folders and root', () => {
e2e.pages.Dashboards.visit();
e2e().contains('A root folder with children').should('be.visible');
// Expand A root folder with children
e2e().get('[aria-label="Expand folder A root folder with children"]').click();
e2e().contains('Nested folder 00').should('be.visible');
// Scroll the page and check visibility of next set of items
e2e().get(`[data-testid="${selectors.pages.BrowseDashboards.table.body}"] > div`).scrollTo(0, 1700);
e2e().contains('Nested folder 59').should('be.visible');
e2e().contains('Nested dashboard 00').should('be.visible');
// Scroll the page and check visibility of next set of items
e2e().get(`[data-testid="${selectors.pages.BrowseDashboards.table.body}"] > div`).scrollTo(0, 3800);
e2e().contains('Nested dashboard 59').should('be.visible');
e2e().contains('Root folder 00').should('be.visible');
// Scroll the page and check visibility of next set of items
e2e().get(`[data-testid="${selectors.pages.BrowseDashboards.table.body}"] > div`).scrollTo(0, 5900);
e2e().contains('Root folder 59').should('be.visible');
e2e().contains('Root dashboard 00').should('be.visible');
// Scroll the page and check visibility of next set of items
e2e().get(`[data-testid="${selectors.pages.BrowseDashboards.table.body}"] > div`).scrollTo(0, 8000);
e2e().contains('Root dashboard 59').should('be.visible');
});
});

View File

@@ -0,0 +1,59 @@
export function makeNewDashboardRequestBody(dashboardName: string, folderUid?: string) {
return {
dashboard: {
annotations: {
list: [
{
builtIn: 1,
datasource: { type: 'grafana', uid: '-- Grafana --' },
enable: true,
hide: true,
iconColor: 'rgba(0, 211, 255, 1)',
name: 'Annotations & Alerts',
type: 'dashboard',
},
],
},
editable: true,
fiscalYearStartMonth: 0,
graphTooltip: 0,
links: [],
liveNow: false,
panels: [
{
datasource: { type: 'testdata', uid: '89_jzlT4k' },
gridPos: { h: 9, w: 12, x: 0, y: 0 },
id: 2,
options: {
code: {
language: 'plaintext',
showLineNumbers: false,
showMiniMap: false,
},
content: '***A nice little happy empty dashboard***',
mode: 'markdown',
},
pluginVersion: '9.4.0-pre',
title: 'Nothing to see here',
type: 'text',
},
],
refresh: '',
revision: 1,
schemaVersion: 38,
style: 'dark',
tags: [],
templating: { list: [] },
time: { from: 'now-6h', to: 'now' },
timepicker: {},
timezone: '',
title: dashboardName,
version: 0,
weekStart: '',
uid: '',
},
message: '',
overwrite: false,
folderUid,
} as const;
}

View File

@@ -270,8 +270,9 @@ export const Pages = {
interval: 'Playlist interval', interval: 'Playlist interval',
itemDelete: 'Delete playlist item', itemDelete: 'Delete playlist item',
}, },
BrowseDashbards: { BrowseDashboards: {
table: { table: {
body: 'data-testid browse-dashboards-table',
row: (uid: string) => `data-testid ${uid} row`, row: (uid: string) => `data-testid ${uid} row`,
checkbox: (uid: string) => `data-testid ${uid} checkbox`, checkbox: (uid: string) => `data-testid ${uid} checkbox`,
}, },

View File

@@ -214,7 +214,7 @@ describe('browse-dashboards BrowseDashboardsPage', () => {
it('selecting an item hides the filters and shows the actions instead', async () => { it('selecting an item hides the filters and shows the actions instead', async () => {
render(<BrowseDashboardsPage {...props} />); render(<BrowseDashboardsPage {...props} />);
const checkbox = await screen.findByTestId(selectors.pages.BrowseDashbards.table.checkbox(dashbdD.item.uid)); const checkbox = await screen.findByTestId(selectors.pages.BrowseDashboards.table.checkbox(dashbdD.item.uid));
await userEvent.click(checkbox); await userEvent.click(checkbox);
// Check the filters are now hidden // Check the filters are now hidden
@@ -229,7 +229,7 @@ describe('browse-dashboards BrowseDashboardsPage', () => {
it('navigating into a child item resets the selected state', async () => { it('navigating into a child item resets the selected state', async () => {
const { rerender } = render(<BrowseDashboardsPage {...props} />); const { rerender } = render(<BrowseDashboardsPage {...props} />);
const checkbox = await screen.findByTestId(selectors.pages.BrowseDashbards.table.checkbox(folderA.item.uid)); const checkbox = await screen.findByTestId(selectors.pages.BrowseDashboards.table.checkbox(folderA.item.uid));
await userEvent.click(checkbox); await userEvent.click(checkbox);
// Check the actions are now visible // Check the actions are now visible
@@ -340,7 +340,7 @@ describe('browse-dashboards BrowseDashboardsPage', () => {
render(<BrowseDashboardsPage {...props} />); render(<BrowseDashboardsPage {...props} />);
const checkbox = await screen.findByTestId( const checkbox = await screen.findByTestId(
selectors.pages.BrowseDashbards.table.checkbox(folderA_folderA.item.uid) selectors.pages.BrowseDashboards.table.checkbox(folderA_folderA.item.uid)
); );
await userEvent.click(checkbox); await userEvent.click(checkbox);

View File

@@ -57,7 +57,7 @@ describe('browse-dashboards BrowseView', () => {
it('checks items when selected', async () => { it('checks items when selected', async () => {
render(<BrowseView canSelect folderUID={undefined} width={WIDTH} height={HEIGHT} />); render(<BrowseView canSelect folderUID={undefined} width={WIDTH} height={HEIGHT} />);
const checkbox = await screen.findByTestId(selectors.pages.BrowseDashbards.table.checkbox(dashbdD.item.uid)); const checkbox = await screen.findByTestId(selectors.pages.BrowseDashboards.table.checkbox(dashbdD.item.uid));
expect(checkbox).not.toBeChecked(); expect(checkbox).not.toBeChecked();
await userEvent.click(checkbox); await userEvent.click(checkbox);
@@ -76,7 +76,7 @@ describe('browse-dashboards BrowseView', () => {
const directChildren = mockTree.filter((v) => v.item.kind !== 'ui' && v.item.parentUID === folderA.item.uid); const directChildren = mockTree.filter((v) => v.item.kind !== 'ui' && v.item.parentUID === folderA.item.uid);
for (const child of directChildren) { for (const child of directChildren) {
const childCheckbox = screen.queryByTestId(selectors.pages.BrowseDashbards.table.checkbox(child.item.uid)); const childCheckbox = screen.queryByTestId(selectors.pages.BrowseDashboards.table.checkbox(child.item.uid));
expect(childCheckbox).toBeChecked(); expect(childCheckbox).toBeChecked();
} }
}); });
@@ -96,7 +96,7 @@ describe('browse-dashboards BrowseView', () => {
const grandchildren = mockTree.filter((v) => v.item.kind !== 'ui' && v.item.parentUID === folderA_folderB.item.uid); const grandchildren = mockTree.filter((v) => v.item.kind !== 'ui' && v.item.parentUID === folderA_folderB.item.uid);
for (const child of grandchildren) { for (const child of grandchildren) {
const childCheckbox = screen.queryByTestId(selectors.pages.BrowseDashbards.table.checkbox(child.item.uid)); const childCheckbox = screen.queryByTestId(selectors.pages.BrowseDashboards.table.checkbox(child.item.uid));
expect(childCheckbox).toBeChecked(); expect(childCheckbox).toBeChecked();
} }
}); });
@@ -112,16 +112,16 @@ describe('browse-dashboards BrowseView', () => {
await clickCheckbox(folderA_folderB_dashbdB.item.uid); await clickCheckbox(folderA_folderB_dashbdB.item.uid);
const itemCheckbox = screen.queryByTestId( const itemCheckbox = screen.queryByTestId(
selectors.pages.BrowseDashbards.table.checkbox(folderA_folderB_dashbdB.item.uid) selectors.pages.BrowseDashboards.table.checkbox(folderA_folderB_dashbdB.item.uid)
); );
expect(itemCheckbox).not.toBeChecked(); expect(itemCheckbox).not.toBeChecked();
const parentCheckbox = screen.queryByTestId( const parentCheckbox = screen.queryByTestId(
selectors.pages.BrowseDashbards.table.checkbox(folderA_folderB.item.uid) selectors.pages.BrowseDashboards.table.checkbox(folderA_folderB.item.uid)
); );
expect(parentCheckbox).not.toBeChecked(); expect(parentCheckbox).not.toBeChecked();
const grandparentCheckbox = screen.queryByTestId(selectors.pages.BrowseDashbards.table.checkbox(folderA.item.uid)); const grandparentCheckbox = screen.queryByTestId(selectors.pages.BrowseDashboards.table.checkbox(folderA.item.uid));
expect(grandparentCheckbox).not.toBeChecked(); expect(grandparentCheckbox).not.toBeChecked();
}); });
@@ -135,12 +135,12 @@ describe('browse-dashboards BrowseView', () => {
await clickCheckbox(folderA_folderB_dashbdB.item.uid); await clickCheckbox(folderA_folderB_dashbdB.item.uid);
const parentCheckbox = screen.queryByTestId( const parentCheckbox = screen.queryByTestId(
selectors.pages.BrowseDashbards.table.checkbox(folderA_folderB.item.uid) selectors.pages.BrowseDashboards.table.checkbox(folderA_folderB.item.uid)
); );
expect(parentCheckbox).not.toBeChecked(); expect(parentCheckbox).not.toBeChecked();
expect(parentCheckbox).toBePartiallyChecked(); expect(parentCheckbox).toBePartiallyChecked();
const grandparentCheckbox = screen.queryByTestId(selectors.pages.BrowseDashbards.table.checkbox(folderA.item.uid)); const grandparentCheckbox = screen.queryByTestId(selectors.pages.BrowseDashboards.table.checkbox(folderA.item.uid));
expect(grandparentCheckbox).not.toBeChecked(); expect(grandparentCheckbox).not.toBeChecked();
expect(grandparentCheckbox).toBePartiallyChecked(); expect(grandparentCheckbox).toBePartiallyChecked();
}); });
@@ -159,18 +159,18 @@ describe('browse-dashboards BrowseView', () => {
}); });
async function expandFolder(uid: string) { async function expandFolder(uid: string) {
const row = screen.getByTestId(selectors.pages.BrowseDashbards.table.row(uid)); const row = screen.getByTestId(selectors.pages.BrowseDashboards.table.row(uid));
const expandButton = getByLabelText(row, 'Expand folder'); const expandButton = getByLabelText(row, /Expand folder/);
await userEvent.click(expandButton); await userEvent.click(expandButton);
} }
async function collapseFolder(uid: string) { async function collapseFolder(uid: string) {
const row = screen.getByTestId(selectors.pages.BrowseDashbards.table.row(uid)); const row = screen.getByTestId(selectors.pages.BrowseDashboards.table.row(uid));
const expandButton = getByLabelText(row, 'Collapse folder'); const expandButton = getByLabelText(row, /Collapse folder/);
await userEvent.click(expandButton); await userEvent.click(expandButton);
} }
async function clickCheckbox(uid: string) { async function clickCheckbox(uid: string) {
const checkbox = screen.getByTestId(selectors.pages.BrowseDashbards.table.checkbox(uid)); const checkbox = screen.getByTestId(selectors.pages.BrowseDashboards.table.checkbox(uid));
await userEvent.click(checkbox); await userEvent.click(checkbox);
} }

View File

@@ -31,7 +31,7 @@ export default function CheckboxCell({
return ( return (
<Checkbox <Checkbox
data-testid={selectors.pages.BrowseDashbards.table.checkbox(item.uid)} data-testid={selectors.pages.BrowseDashboards.table.checkbox(item.uid)}
value={state === SelectionState.Selected} value={state === SelectionState.Selected}
indeterminate={state === SelectionState.Mixed} indeterminate={state === SelectionState.Mixed}
onChange={(ev) => onItemSelectionChange?.(item, ev.currentTarget.checked)} onChange={(ev) => onItemSelectionChange?.(item, ev.currentTarget.checked)}

View File

@@ -44,7 +44,7 @@ describe('browse-dashboards DashboardsTree', () => {
); );
expect(screen.queryByText(dashboard.item.title)).toBeInTheDocument(); expect(screen.queryByText(dashboard.item.title)).toBeInTheDocument();
expect(screen.queryByText(assertIsDefined(dashboard.item.tags)[0])).toBeInTheDocument(); expect(screen.queryByText(assertIsDefined(dashboard.item.tags)[0])).toBeInTheDocument();
expect(screen.getByTestId(selectors.pages.BrowseDashbards.table.checkbox(dashboard.item.uid))).toBeInTheDocument(); expect(screen.getByTestId(selectors.pages.BrowseDashboards.table.checkbox(dashboard.item.uid))).toBeInTheDocument();
}); });
it('does not render checkbox when disabled', () => { it('does not render checkbox when disabled', () => {
@@ -63,7 +63,7 @@ describe('browse-dashboards DashboardsTree', () => {
/> />
); );
expect( expect(
screen.queryByTestId(selectors.pages.BrowseDashbards.table.checkbox(dashboard.item.uid)) screen.queryByTestId(selectors.pages.BrowseDashboards.table.checkbox(dashboard.item.uid))
).not.toBeInTheDocument(); ).not.toBeInTheDocument();
}); });
@@ -101,7 +101,7 @@ describe('browse-dashboards DashboardsTree', () => {
requestLoadMore={requestLoadMore} requestLoadMore={requestLoadMore}
/> />
); );
const folderButton = screen.getByLabelText('Expand folder'); const folderButton = screen.getByLabelText(`Expand folder ${folder.item.title}`);
await userEvent.click(folderButton); await userEvent.click(folderButton);
expect(handler).toHaveBeenCalledWith(folder.item.uid, true); expect(handler).toHaveBeenCalledWith(folder.item.uid, true);

View File

@@ -147,7 +147,7 @@ export function DashboardsTree({
); );
})} })}
<div {...getTableBodyProps()}> <div {...getTableBodyProps()} data-testid={selectors.pages.BrowseDashboards.table.body}>
<InfiniteLoader <InfiniteLoader
ref={infiniteLoaderRef} ref={infiniteLoaderRef}
itemCount={items.length} itemCount={items.length}
@@ -196,7 +196,7 @@ function VirtualListRow({ index, style, data }: VirtualListRowProps) {
<div <div
{...row.getRowProps({ style })} {...row.getRowProps({ style })}
className={cx(styles.row, styles.bodyRow)} className={cx(styles.row, styles.bodyRow)}
data-testid={selectors.pages.BrowseDashbards.table.row(row.original.item.uid)} data-testid={selectors.pages.BrowseDashboards.table.row(row.original.item.uid)}
> >
{row.cells.map((cell) => { {row.cells.map((cell) => {
const { key, ...cellProps } = cell.getCellProps(); const { key, ...cellProps } = cell.getCellProps();

View File

@@ -59,7 +59,7 @@ export function NameCell({ row: { original: data }, onFolderClick }: NameCellPro
onFolderClick(item.uid, !isOpen); onFolderClick(item.uid, !isOpen);
}} }}
name={isOpen ? 'angle-down' : 'angle-right'} name={isOpen ? 'angle-down' : 'angle-right'}
aria-label={isOpen ? 'Collapse folder' : 'Expand folder'} aria-label={isOpen ? `Collapse folder ${item.title}` : `Expand folder ${item.title}`}
/> />
) : ( ) : (
<span className={styles.folderButtonSpacer} /> <span className={styles.folderButtonSpacer} />