mirror of https://github.com/grafana/grafana.git
Dashboards: User journey E2Es (#109049)
* wip * wip * wip * wip * scope e2es * wip * wip * wip * wip * wip * wip * wip * wip * add dashboard view tests * mods cujs * wip refactor * remove timeouts * fixes * betterer * fixes * refactor * refactor * fix * use type instead of any * betterer * PR mods + codeowners * CODEOWNERS * readme lint
This commit is contained in:
parent
4d818292d8
commit
27b3137baf
|
@ -410,7 +410,9 @@
|
|||
/e2e/ @grafana/grafana-frontend-platform
|
||||
/e2e-playwright/cloud-plugins-suite/ @grafana/partner-datasources
|
||||
/e2e-playwright/dashboard-new-layouts/ @grafana/dashboards-squad
|
||||
/e2e-playwright/dashboard-cujs/ @grafana/dashboards-squad
|
||||
/e2e-playwright/dashboards-search-suite/ @grafana/dashboards-squad
|
||||
/e2e-playwright/dashboards/cujs/ @grafana/dashboards-squad
|
||||
/e2e-playwright/dashboards/DashboardLiveTest.json @grafana/dashboards-squad
|
||||
/e2e-playwright/dashboards/DataLinkWithoutSlugTest.json @grafana/dashboards-squad
|
||||
/e2e-playwright/dashboards/PanelSandboxDashboard.json @grafana/plugins-platform-frontend
|
||||
|
|
|
@ -0,0 +1,39 @@
|
|||
# Dashboard Critical User Journeys (CUJs) E2E Tests
|
||||
|
||||
This directory contains end-to-end tests for critical user journeys related to dashboard functionality in Grafana, specifically testing AdHoc Filters, GroupBy Variables, Scopes, and Dashboard Navigation. The test suite validates core dashboard workflows including filtering data across dashboards, autocomplete functionality, operator selection, grouping across multiple dimensions, scope selection and persistence, and navigation between dashboards with state management. Tests use three pre-configured dashboard JSON files (cuj-dashboard-1, cuj-dashboard-2, cuj-dashboard-3) that contain text panels displaying variable values, AdHoc filter variables, GroupBy variables, and time range controls connected to a Prometheus datasource.
|
||||
|
||||
## Environment Flags
|
||||
|
||||
### `NO_DASHBOARD_IMPORT`
|
||||
|
||||
**Default**: `false`
|
||||
|
||||
When set to `true`, skips importing test dashboards during global setup. Use this when running against a live Grafana instance that already has the required dashboards with matching UIDs (`cuj-dashboard-1`, `cuj-dashboard-2`, `cuj-dashboard-3`).
|
||||
|
||||
```bash
|
||||
NO_DASHBOARD_IMPORT=true yarn e2e:playwright
|
||||
```
|
||||
|
||||
### `API_CONFIG_PATH`
|
||||
|
||||
**Default**: `../dashboards/cujs/config.json`
|
||||
|
||||
Configures the path to the API mocking configuration file. This enables dynamic API endpoint configuration for different environments.
|
||||
|
||||
If the `API_CONFIG_PATH` is not set, the test suite will use mocked responses for API calls using the default configuration, which has settings for the default testing environment. If set, the test suite will make real API calls instead of using mocks, based on the configuration provided in the specified file. This configuration would be used only in live data scenarios where the endpoints might differ due to testing on different dashboards that might use different DataSources which furthermore might have different API endpoints.
|
||||
|
||||
The config file should contain endpoint glob patterns for labels and values APIs. These endpoints are used to fetch labels (keys) and values for the AdHocFilters and the GroupBy variables. The pattern should be a string glob pattern, e.g.: '\*\*/resources/\*\*/labels\*'
|
||||
|
||||
```bash
|
||||
API_CONFIG_PATH=/path/to/custom-config.json yarn e2e:playwright
|
||||
```
|
||||
|
||||
## Global Setup and Teardown
|
||||
|
||||
### Setup Process (`global-setup.spec.ts`)
|
||||
|
||||
The global setup imports three CUJ dashboard JSON files unless `NO_DASHBOARD_IMPORT` is true. It posts each dashboard to `/api/dashboards/import` with overwrite enabled, stores the returned dashboard UIDs in `process.env.DASHBOARD_UIDS` via `setDashboardUIDs()`, and handles cases where dashboards already exist by overwriting them.
|
||||
|
||||
### Teardown Process (`global-teardown.spec.ts`)
|
||||
|
||||
The teardown retrieves stored dashboard UIDs using `getDashboardUIDs()`, deletes each dashboard via `DELETE /api/dashboards/uid/{uid}`, and cleans up the environment state with `clearDashboardUIDs()`. The dashboard UID state management is handled through `dashboardUidsState.ts` which provides functions to store, retrieve, and clear UIDs in the process environment.
|
|
@ -0,0 +1,241 @@
|
|||
import { test, expect } from '@grafana/plugin-e2e';
|
||||
|
||||
import { setScopes } from '../utils/scope-helpers';
|
||||
|
||||
import {
|
||||
getAdHocFilterOptionValues,
|
||||
getAdHocFilterPills,
|
||||
getAdHocFilterRestoreButton,
|
||||
getAdhocFiltersInput,
|
||||
getMarkdownHTMLContent,
|
||||
getScopesSelectorInput,
|
||||
waitForAdHocOption,
|
||||
} from './cuj-selectors';
|
||||
import { prepareAPIMocks } from './utils';
|
||||
|
||||
export const DASHBOARD_UNDER_TEST = 'cuj-dashboard-1';
|
||||
|
||||
test.use({
|
||||
featureToggles: {
|
||||
scopeFilters: true,
|
||||
groupByVariable: true,
|
||||
reloadDashboardsOnParamsChange: true,
|
||||
},
|
||||
});
|
||||
|
||||
test.describe(
|
||||
'AdHoc Filters CUJs',
|
||||
{
|
||||
tag: ['@dashboard-cujs'],
|
||||
},
|
||||
() => {
|
||||
test('Filter data on a dashboard', async ({ page, selectors, gotoDashboardPage }) => {
|
||||
const apiMocks = await prepareAPIMocks(page);
|
||||
const adHocFilterPills = getAdHocFilterPills(page);
|
||||
const scopesSelectorInput = getScopesSelectorInput(page);
|
||||
|
||||
await test.step('1.Apply filtering to a whole dashboard', async () => {
|
||||
const dashboardPage = await gotoDashboardPage({ uid: DASHBOARD_UNDER_TEST });
|
||||
|
||||
await expect(adHocFilterPills.first()).toBeVisible();
|
||||
|
||||
expect(await adHocFilterPills.count()).toBe(2);
|
||||
|
||||
const adHocVariable = dashboardPage
|
||||
.getByGrafanaSelector(selectors.pages.Dashboard.SubMenu.submenuItemLabels('adHoc'))
|
||||
.locator('..')
|
||||
.locator('input');
|
||||
|
||||
const labelsResponsePromise = page.waitForResponse(apiMocks.labels);
|
||||
await adHocVariable.click();
|
||||
await labelsResponsePromise;
|
||||
await adHocVariable.press('Enter');
|
||||
await waitForAdHocOption(page);
|
||||
const valuesResponsePromise = page.waitForResponse(apiMocks.values);
|
||||
await adHocVariable.press('Enter');
|
||||
await valuesResponsePromise;
|
||||
await waitForAdHocOption(page);
|
||||
await adHocVariable.press('Enter');
|
||||
|
||||
expect(await adHocFilterPills.count()).toBe(3);
|
||||
|
||||
const pills = await adHocFilterPills.allTextContents();
|
||||
const processedPills = pills
|
||||
.map((p) => {
|
||||
const parts = p.split(' ');
|
||||
return `${parts[0]}${parts[1]}"${parts[2]}"`;
|
||||
})
|
||||
.join(',');
|
||||
|
||||
// assert the panel is visible and has the correct value
|
||||
const markdownContent = await getMarkdownHTMLContent(dashboardPage, selectors);
|
||||
await expect(markdownContent).toContainText(`AdHocVar: ${processedPills}`);
|
||||
});
|
||||
|
||||
await test.step('2.Autocomplete for the filter values', async () => {
|
||||
const dashboardPage = await gotoDashboardPage({ uid: DASHBOARD_UNDER_TEST });
|
||||
|
||||
const adHocVariable = getAdhocFiltersInput(dashboardPage, selectors);
|
||||
|
||||
const labelsResponsePromise = page.waitForResponse(apiMocks.labels);
|
||||
await adHocVariable.click();
|
||||
await labelsResponsePromise;
|
||||
await adHocVariable.press('Enter');
|
||||
await waitForAdHocOption(page);
|
||||
const valuesResponsePromise = page.waitForResponse(apiMocks.values);
|
||||
await adHocVariable.press('Enter');
|
||||
await valuesResponsePromise;
|
||||
|
||||
const valuesLocator = getAdHocFilterOptionValues(page);
|
||||
const valuesCount = await valuesLocator.count();
|
||||
const firstValue = await valuesLocator.first().textContent();
|
||||
|
||||
await adHocVariable.fill(firstValue!.slice(0, -1));
|
||||
|
||||
await waitForAdHocOption(page);
|
||||
|
||||
const newValuesCount = await valuesLocator.count();
|
||||
expect(newValuesCount).toBeLessThan(valuesCount);
|
||||
//exclude the custom value
|
||||
expect(newValuesCount).toBeGreaterThan(1);
|
||||
});
|
||||
|
||||
await test.step('3.Choose operators on the filters', async () => {
|
||||
const dashboardPage = await gotoDashboardPage({ uid: DASHBOARD_UNDER_TEST });
|
||||
|
||||
await expect(adHocFilterPills.first()).toBeVisible();
|
||||
|
||||
expect(await adHocFilterPills.count()).toBe(2);
|
||||
|
||||
const adHocVariable = getAdhocFiltersInput(dashboardPage, selectors);
|
||||
|
||||
const labelsResponsePromise = page.waitForResponse(apiMocks.labels);
|
||||
await adHocVariable.click();
|
||||
await labelsResponsePromise;
|
||||
await adHocVariable.press('Enter');
|
||||
await waitForAdHocOption(page);
|
||||
await adHocVariable.press('ArrowDown');
|
||||
await adHocVariable.press('ArrowDown');
|
||||
await adHocVariable.press('ArrowDown');
|
||||
await adHocVariable.press('ArrowDown');
|
||||
const valuesResponsePromise = page.waitForResponse(apiMocks.values);
|
||||
await adHocVariable.press('Enter');
|
||||
await valuesResponsePromise;
|
||||
await adHocVariable.press('Enter');
|
||||
|
||||
expect(await adHocFilterPills.count()).toBe(3);
|
||||
|
||||
const pills = await adHocFilterPills.allTextContents();
|
||||
const processedPills = pills
|
||||
.map((p) => {
|
||||
const parts = p.split(' ');
|
||||
return `${parts[0]}${parts[1]}"${parts[2]}"`;
|
||||
})
|
||||
.join(',');
|
||||
|
||||
// assert the panel is visible and has the correct value
|
||||
const markdownContent = await getMarkdownHTMLContent(dashboardPage, selectors);
|
||||
await expect(markdownContent).toContainText(`AdHocVar: ${processedPills}`);
|
||||
// regex operator applied to the filter
|
||||
await expect(markdownContent).toContainText(`=~`);
|
||||
});
|
||||
|
||||
await test.step('4.Edit and restore default filters applied to the dashboard', async () => {
|
||||
const dashboardPage = await gotoDashboardPage({ uid: DASHBOARD_UNDER_TEST });
|
||||
|
||||
const defaultDashboardFilter = adHocFilterPills.first();
|
||||
const pillText = await defaultDashboardFilter.textContent();
|
||||
|
||||
const adHocVariable = getAdhocFiltersInput(dashboardPage, selectors).first();
|
||||
|
||||
await defaultDashboardFilter.click();
|
||||
await adHocVariable.fill('new value');
|
||||
await adHocVariable.press('Enter');
|
||||
|
||||
expect(await defaultDashboardFilter.textContent()).not.toBe(pillText);
|
||||
|
||||
const restoreButton = getAdHocFilterRestoreButton(page, 'dashboard');
|
||||
await restoreButton.click();
|
||||
|
||||
expect(await defaultDashboardFilter.textContent()).toBe(pillText);
|
||||
});
|
||||
|
||||
await test.step('5.Edit and restore filters implied by scope', async () => {
|
||||
const dashboardPage = await gotoDashboardPage({ uid: DASHBOARD_UNDER_TEST });
|
||||
|
||||
await expect(adHocFilterPills.first()).toBeVisible();
|
||||
|
||||
expect(await adHocFilterPills.count()).toBe(2);
|
||||
|
||||
await setScopes(page);
|
||||
|
||||
await expect(scopesSelectorInput).toHaveValue(/.+/);
|
||||
|
||||
expect(await adHocFilterPills.count()).toBe(3);
|
||||
|
||||
const defaultDashboardFilter = adHocFilterPills.first();
|
||||
const pillText = await defaultDashboardFilter.textContent();
|
||||
|
||||
const adHocVariable = getAdhocFiltersInput(dashboardPage, selectors).first();
|
||||
|
||||
await defaultDashboardFilter.click();
|
||||
await adHocVariable.fill('new value');
|
||||
await adHocVariable.press('Enter');
|
||||
|
||||
expect(await defaultDashboardFilter.textContent()).not.toBe(pillText);
|
||||
|
||||
const restoreButton = getAdHocFilterRestoreButton(page, 'scope');
|
||||
await restoreButton.click();
|
||||
|
||||
expect(await defaultDashboardFilter.textContent()).toBe(pillText);
|
||||
});
|
||||
|
||||
await test.step('6.Add and edit filters through keyboard', async () => {
|
||||
const dashboardPage = await gotoDashboardPage({ uid: DASHBOARD_UNDER_TEST });
|
||||
|
||||
await expect(adHocFilterPills.first()).toBeVisible();
|
||||
|
||||
expect(await adHocFilterPills.count()).toBe(2);
|
||||
|
||||
const adHocVariable = getAdhocFiltersInput(dashboardPage, selectors);
|
||||
|
||||
const labelsResponsePromise = page.waitForResponse(apiMocks.labels);
|
||||
await adHocVariable.click();
|
||||
await labelsResponsePromise;
|
||||
await adHocVariable.press('Enter');
|
||||
await waitForAdHocOption(page);
|
||||
const valuesResponsePromise = page.waitForResponse(apiMocks.values);
|
||||
await adHocVariable.press('Enter');
|
||||
await valuesResponsePromise;
|
||||
const secondLabelsPromise = page.waitForResponse(apiMocks.labels);
|
||||
await adHocVariable.press('Enter');
|
||||
|
||||
// add another filter
|
||||
await secondLabelsPromise;
|
||||
await adHocVariable.press('ArrowDown');
|
||||
await adHocVariable.press('Enter');
|
||||
// arrow down to multivalue op
|
||||
await adHocVariable.press('ArrowDown');
|
||||
await adHocVariable.press('ArrowDown');
|
||||
await adHocVariable.press('ArrowDown');
|
||||
const secondValuesResponsePromise = page.waitForResponse(apiMocks.values);
|
||||
await adHocVariable.press('Enter');
|
||||
await secondValuesResponsePromise;
|
||||
//select firs value, then arrow down to another
|
||||
await adHocVariable.press('Enter');
|
||||
await adHocVariable.press('ArrowDown');
|
||||
await adHocVariable.press('Enter');
|
||||
//escape applies it
|
||||
await adHocVariable.press('Escape');
|
||||
|
||||
expect(await adHocFilterPills.count()).toBe(4);
|
||||
|
||||
//remove last value through keyboard
|
||||
await page.keyboard.press('Shift+Tab');
|
||||
await page.keyboard.press('Enter');
|
||||
|
||||
expect(await adHocFilterPills.count()).toBe(3);
|
||||
});
|
||||
});
|
||||
}
|
||||
);
|
|
@ -0,0 +1,82 @@
|
|||
import { Page } from '@playwright/test';
|
||||
|
||||
import { expect, DashboardPage, E2ESelectorGroups } from '@grafana/plugin-e2e';
|
||||
|
||||
export function getAdHocFilterPills(page: Page) {
|
||||
return page.getByLabel(/^Edit filter with key/);
|
||||
}
|
||||
|
||||
export async function waitForAdHocOption(page: Page) {
|
||||
await page.waitForSelector('[role="option"]', { state: 'visible' });
|
||||
}
|
||||
|
||||
export async function getMarkdownHTMLContent(page: DashboardPage, selectors: E2ESelectorGroups) {
|
||||
const panelContent = page.getByGrafanaSelector(selectors.components.Panels.Panel.content).first();
|
||||
await expect(panelContent).toBeVisible();
|
||||
return panelContent.locator('.markdown-html');
|
||||
}
|
||||
|
||||
export function getAdhocFiltersInput(page: DashboardPage, selectors: E2ESelectorGroups) {
|
||||
return page
|
||||
.getByGrafanaSelector(selectors.pages.Dashboard.SubMenu.submenuItemLabels('adHoc'))
|
||||
.locator('..')
|
||||
.locator('input');
|
||||
}
|
||||
|
||||
export function getGroupByInput(page: DashboardPage, selectors: E2ESelectorGroups) {
|
||||
return page
|
||||
.getByGrafanaSelector(selectors.pages.Dashboard.SubMenu.submenuItemLabels('groupBy'))
|
||||
.locator('..')
|
||||
.locator('input');
|
||||
}
|
||||
|
||||
export function getAdHocFilterOptionValues(page: Page) {
|
||||
return page.getByTestId(/^data-testid ad hoc filter option value/);
|
||||
}
|
||||
|
||||
export function getAdHocFilterRestoreButton(page: Page, type: string) {
|
||||
if (type === 'dashboard') {
|
||||
return page.getByLabel('Restore the value set by this dashboard.');
|
||||
}
|
||||
|
||||
if (type === 'scope') {
|
||||
return page.getByLabel('Restore the value set by your selected scope.');
|
||||
}
|
||||
|
||||
return page.getByLabel('Restore filter to its original value.');
|
||||
}
|
||||
|
||||
export function getGroupByRestoreButton(page: Page) {
|
||||
return page.getByLabel('Restore groupby set by this dashboard.');
|
||||
}
|
||||
|
||||
export function getScopesSelectorInput(page: Page) {
|
||||
return page.getByTestId('scopes-selector-input');
|
||||
}
|
||||
|
||||
export function getRecentScopesSelector(page: Page) {
|
||||
return page.getByTestId('scopes-selector-recent-scopes-section');
|
||||
}
|
||||
|
||||
export function getScopeTreeCheckboxes(page: Page) {
|
||||
return page.locator('input[type="checkbox"][data-testid^="scopes-tree"]');
|
||||
}
|
||||
|
||||
export function getScopesDashboards(page: Page) {
|
||||
return page.locator('[data-testid^="scopes-dashboards-"][role="treeitem"]');
|
||||
}
|
||||
|
||||
export function getScopesDashboardsSearchInput(page: Page) {
|
||||
return page.getByTestId('scopes-dashboards-search');
|
||||
}
|
||||
|
||||
export function getGroupByValues(page: Page) {
|
||||
return page
|
||||
.getByTestId(/^GroupBySelect-/)
|
||||
.first()
|
||||
.locator('div:has(+ button)');
|
||||
}
|
||||
|
||||
export function getGroupByOptions(page: Page) {
|
||||
return page.getByTestId('data-testid Select option');
|
||||
}
|
|
@ -0,0 +1,156 @@
|
|||
import { test, expect } from '@grafana/plugin-e2e';
|
||||
|
||||
import { setScopes } from '../utils/scope-helpers';
|
||||
|
||||
import {
|
||||
getAdHocFilterPills,
|
||||
getGroupByInput,
|
||||
getGroupByValues,
|
||||
getMarkdownHTMLContent,
|
||||
getScopesDashboards,
|
||||
getScopesDashboardsSearchInput,
|
||||
getScopesSelectorInput,
|
||||
} from './cuj-selectors';
|
||||
|
||||
test.use({
|
||||
featureToggles: {
|
||||
scopeFilters: true,
|
||||
groupByVariable: true,
|
||||
reloadDashboardsOnParamsChange: true,
|
||||
},
|
||||
});
|
||||
|
||||
export const DASHBOARD_UNDER_TEST = 'cuj-dashboard-1';
|
||||
export const NAVIGATE_TO = 'cuj-dashboard-2';
|
||||
|
||||
test.describe(
|
||||
'Dashboard navigation CUJs',
|
||||
{
|
||||
tag: ['@dashboard-cujs'],
|
||||
},
|
||||
() => {
|
||||
test('Navigate between dashboards', async ({ page, gotoDashboardPage, selectors }) => {
|
||||
const scopeSelectorInput = getScopesSelectorInput(page);
|
||||
const scopesDashboards = getScopesDashboards(page);
|
||||
const scopesDashboardsSearchInput = getScopesDashboardsSearchInput(page);
|
||||
const adhocFilterPills = getAdHocFilterPills(page);
|
||||
const groupByValues = getGroupByValues(page);
|
||||
|
||||
await test.step('1.Search dashboard', async () => {
|
||||
await gotoDashboardPage({ uid: DASHBOARD_UNDER_TEST });
|
||||
|
||||
await setScopes(page);
|
||||
|
||||
await expect(scopeSelectorInput).toHaveValue(/.+/);
|
||||
|
||||
const firstDbName = await scopesDashboards.first().textContent();
|
||||
const scopeDashboardsCount = await scopesDashboards.count();
|
||||
|
||||
expect(scopeDashboardsCount).toBeGreaterThan(0);
|
||||
|
||||
await scopesDashboardsSearchInput.fill(firstDbName!.trim().slice(0, 5));
|
||||
|
||||
await expect(scopesDashboards).not.toHaveCount(scopeDashboardsCount);
|
||||
});
|
||||
|
||||
await test.step('2.Time selection persisting', async () => {
|
||||
const dashboardPage = await gotoDashboardPage({ uid: DASHBOARD_UNDER_TEST });
|
||||
|
||||
await setScopes(page);
|
||||
|
||||
await expect(scopeSelectorInput).toHaveValue(/.+/);
|
||||
|
||||
// assert the panel is visible and has the correct value
|
||||
const markdownContent = await getMarkdownHTMLContent(dashboardPage, selectors);
|
||||
await expect(markdownContent).toContainText(`now-6h`);
|
||||
|
||||
const timePickerButton = page.getByTestId(selectors.components.TimePicker.openButton);
|
||||
|
||||
await timePickerButton.click();
|
||||
const label = page.getByText('Last 12 hours');
|
||||
await label.click();
|
||||
|
||||
await expect(markdownContent).toContainText(`now-12h`);
|
||||
|
||||
await scopesDashboards.first().click();
|
||||
await page.waitForURL('**/d/**');
|
||||
|
||||
await expect(markdownContent).toBeVisible();
|
||||
await expect(markdownContent).toContainText(`now-12h`);
|
||||
});
|
||||
|
||||
await test.step('3.See filter/groupby selection persisting when navigating from dashboard to dashboard', async () => {
|
||||
const dashboardPage = await gotoDashboardPage({ uid: NAVIGATE_TO });
|
||||
|
||||
await setScopes(page, { title: 'CUJ Dashboard 3', uid: 'cuj-dashboard-3' });
|
||||
|
||||
await expect(scopeSelectorInput).toHaveValue(/.+/);
|
||||
|
||||
const pills = await adhocFilterPills.allTextContents();
|
||||
const processedPills = pills
|
||||
.map((p) => {
|
||||
const parts = p.split(' ');
|
||||
return `${parts[0]}${parts[1]}"${parts[2]}"`;
|
||||
})
|
||||
.join(',');
|
||||
|
||||
// assert the panel is visible and has the correct value
|
||||
const markdownContent = await getMarkdownHTMLContent(dashboardPage, selectors);
|
||||
// no groupBy value
|
||||
await expect(markdownContent).toContainText(`GroupByVar: \n\nAdHocVar: ${processedPills}`);
|
||||
|
||||
const groupByVariable = getGroupByInput(dashboardPage, selectors);
|
||||
|
||||
// add a custom groupBy value
|
||||
await groupByVariable.click();
|
||||
await groupByVariable.fill('dev');
|
||||
await groupByVariable.press('Enter');
|
||||
await groupByVariable.press('Escape');
|
||||
|
||||
await expect(scopesDashboards.first()).toBeVisible();
|
||||
await scopesDashboards.first().click();
|
||||
await page.waitForURL('**/d/**');
|
||||
|
||||
//all values are set after dashboard switch
|
||||
await expect(markdownContent).toContainText(`GroupByVar: dev\n\nAdHocVar: ${processedPills}`);
|
||||
});
|
||||
|
||||
await test.step('4.Unmodified default filters and groupBy keys are not propagated to a different dashboard', async () => {
|
||||
const dashboardPage = await gotoDashboardPage({ uid: DASHBOARD_UNDER_TEST });
|
||||
|
||||
await setScopes(page, { title: 'CUJ Dashboard 2', uid: 'cuj-dashboard-2' });
|
||||
|
||||
await expect(scopeSelectorInput).toHaveValue(/.+/);
|
||||
|
||||
const pillCount = await adhocFilterPills.count();
|
||||
const pillTexts = await adhocFilterPills.allTextContents();
|
||||
const processedPills = pillTexts
|
||||
.map((p) => {
|
||||
const parts = p.split(' ');
|
||||
return `${parts[0]}${parts[1]}"${parts[2]}"`;
|
||||
})
|
||||
.join(',');
|
||||
|
||||
const groupByCount = await groupByValues.count();
|
||||
const selectedValues = (await groupByValues.allTextContents()).join(', ');
|
||||
|
||||
// assert the panel is visible and has the correct value
|
||||
const markdownContent = await getMarkdownHTMLContent(dashboardPage, selectors);
|
||||
|
||||
const oldFilters = `GroupByVar: ${selectedValues}\n\nAdHocVar: ${processedPills}`;
|
||||
await expect(markdownContent).toContainText(oldFilters);
|
||||
|
||||
await expect(scopesDashboards.first()).toBeVisible();
|
||||
await scopesDashboards.first().click();
|
||||
await page.waitForURL('**/d/**');
|
||||
|
||||
const newPillCount = await adhocFilterPills.count();
|
||||
const newGroupByCount = await groupByValues.count();
|
||||
|
||||
expect(newPillCount).not.toEqual(pillCount);
|
||||
expect(newGroupByCount).not.toEqual(groupByCount);
|
||||
expect(newGroupByCount).toBe(0);
|
||||
});
|
||||
});
|
||||
}
|
||||
);
|
|
@ -0,0 +1,178 @@
|
|||
import { test, expect } from '@grafana/plugin-e2e';
|
||||
|
||||
test.use({
|
||||
featureToggles: {
|
||||
scopeFilters: true,
|
||||
groupByVariable: true,
|
||||
reloadDashboardsOnParamsChange: true,
|
||||
},
|
||||
});
|
||||
|
||||
export const DASHBOARD_UNDER_TEST = 'cuj-dashboard-1';
|
||||
export const PANEL_UNDER_TEST = 'Panel Title';
|
||||
|
||||
test.describe(
|
||||
'Dashboard view CUJs',
|
||||
{
|
||||
tag: ['@dashboard-cujs'],
|
||||
},
|
||||
() => {
|
||||
test('View a dashboard', async ({ page, gotoDashboardPage, selectors }) => {
|
||||
await test.step('1.Top level selectors', async () => {
|
||||
const dashboardPage = await gotoDashboardPage({ uid: DASHBOARD_UNDER_TEST });
|
||||
|
||||
const groupByVariable = dashboardPage.getByGrafanaSelector(
|
||||
selectors.pages.Dashboard.SubMenu.submenuItemLabels('groupBy')
|
||||
);
|
||||
|
||||
const adHocVariable = dashboardPage.getByGrafanaSelector(
|
||||
selectors.pages.Dashboard.SubMenu.submenuItemLabels('adHoc')
|
||||
);
|
||||
|
||||
expect(groupByVariable).toBeVisible();
|
||||
expect(adHocVariable).toBeVisible();
|
||||
});
|
||||
|
||||
await test.step('2.View individual panel', async () => {
|
||||
const dashboardPage = await gotoDashboardPage({ uid: DASHBOARD_UNDER_TEST });
|
||||
|
||||
const viewPanelBreadcrumb = dashboardPage.getByGrafanaSelector(
|
||||
selectors.components.Breadcrumbs.breadcrumb('View panel')
|
||||
);
|
||||
|
||||
await expect(viewPanelBreadcrumb).not.toBeVisible();
|
||||
|
||||
const panelTitle = dashboardPage.getByGrafanaSelector(
|
||||
selectors.components.Panels.Panel.title(PANEL_UNDER_TEST)
|
||||
);
|
||||
await expect(panelTitle).toBeVisible();
|
||||
// Open panel menu and click edit
|
||||
await panelTitle.hover();
|
||||
await dashboardPage.getByGrafanaSelector(selectors.components.Panels.Panel.menu(PANEL_UNDER_TEST)).click();
|
||||
await dashboardPage.getByGrafanaSelector(selectors.components.Panels.Panel.menuItems('View')).click();
|
||||
|
||||
await expect(viewPanelBreadcrumb).toBeVisible();
|
||||
|
||||
await dashboardPage
|
||||
.getByGrafanaSelector(selectors.components.NavToolbar.editDashboard.backToDashboardButton)
|
||||
.click();
|
||||
|
||||
await expect(viewPanelBreadcrumb).not.toBeVisible();
|
||||
});
|
||||
|
||||
await test.step('3.Set time range for the dashboard', async () => {
|
||||
const dashboardPage = await gotoDashboardPage({ uid: DASHBOARD_UNDER_TEST });
|
||||
|
||||
const timePickerButton = dashboardPage.getByGrafanaSelector(selectors.components.TimePicker.openButton);
|
||||
|
||||
await test.step('3.1.Click on Quick range', async () => {
|
||||
await page.mouse.move(0, 0);
|
||||
await expect.soft(timePickerButton).toContainText('Last 6 hours');
|
||||
await timePickerButton.click();
|
||||
|
||||
const label = page.getByText('Last 5 minutes');
|
||||
await label.click();
|
||||
|
||||
expect.soft(await timePickerButton.textContent()).toContain('Last 5 minutes');
|
||||
});
|
||||
|
||||
await test.step('3.2.Set absolute time range', async () => {
|
||||
await timePickerButton.click();
|
||||
await dashboardPage
|
||||
.getByGrafanaSelector(selectors.components.TimePicker.fromField)
|
||||
.fill('2024-01-01 00:00:00');
|
||||
await dashboardPage.getByGrafanaSelector(selectors.components.TimePicker.toField).fill('2024-01-01 23:59:59');
|
||||
await dashboardPage.getByGrafanaSelector(selectors.components.TimePicker.applyTimeRange).click();
|
||||
|
||||
expect.soft(await timePickerButton.textContent()).toContain('2024-01-01 00:00:00 to 2024-01-01 23:59:59');
|
||||
});
|
||||
|
||||
await test.step('3.3.Change time zone', async () => {
|
||||
await timePickerButton.click();
|
||||
await dashboardPage
|
||||
.getByGrafanaSelector(selectors.components.TimeZonePicker.changeTimeSettingsButton)
|
||||
.click();
|
||||
|
||||
const timeZoneSelectionArea = page.locator('section[aria-label="Time zone selection"]');
|
||||
expect(timeZoneSelectionArea).toBeVisible();
|
||||
expect(await timeZoneSelectionArea.textContent()).toContain('Browser Time');
|
||||
|
||||
await dashboardPage.getByGrafanaSelector(selectors.components.TimeZonePicker.containerV2).click();
|
||||
const label = page.getByText('Coordinated Universal Time');
|
||||
await label.click();
|
||||
|
||||
expect(await timeZoneSelectionArea.textContent()).toContain('Coordinated Universal Time');
|
||||
await timePickerButton.click();
|
||||
});
|
||||
|
||||
await test.step('3.4.Navigate time range', async () => {
|
||||
await timePickerButton.click();
|
||||
|
||||
await dashboardPage
|
||||
.getByGrafanaSelector(selectors.components.TimePicker.fromField)
|
||||
.fill('2024-01-01 08:30:00');
|
||||
await dashboardPage.getByGrafanaSelector(selectors.components.TimePicker.toField).fill('2024-01-01 08:40:00');
|
||||
await dashboardPage.getByGrafanaSelector(selectors.components.TimePicker.applyTimeRange).click();
|
||||
|
||||
expect.soft(await timePickerButton.textContent()).toContain('2024-01-01 08:30:00 to 2024-01-01 08:40:00');
|
||||
|
||||
const forwardBtn = page.locator('button[aria-label="Move time range forwards"]');
|
||||
const backwardBtn = page.locator('button[aria-label="Move time range backwards"]');
|
||||
|
||||
await forwardBtn.click();
|
||||
|
||||
expect.soft(await timePickerButton.textContent()).toContain('2024-01-01 08:35:00 to 2024-01-01 08:45:00');
|
||||
|
||||
await backwardBtn.click();
|
||||
await backwardBtn.click();
|
||||
|
||||
expect.soft(await timePickerButton.textContent()).toContain('2024-01-01 08:25:00 to 2024-01-01 08:35:00');
|
||||
});
|
||||
});
|
||||
|
||||
await test.step('4.Force refresh', async () => {
|
||||
const dashboardPage = await gotoDashboardPage({ uid: DASHBOARD_UNDER_TEST });
|
||||
|
||||
const refreshBtn = dashboardPage.getByGrafanaSelector(selectors.components.RefreshPicker.runButtonV2);
|
||||
|
||||
const panelContent = dashboardPage.getByGrafanaSelector(selectors.components.Panels.Panel.content).nth(1);
|
||||
|
||||
const panelContents = await panelContent.textContent();
|
||||
|
||||
await refreshBtn.click();
|
||||
|
||||
await page.waitForLoadState('networkidle');
|
||||
|
||||
expect(await panelContent.textContent()).not.toBe(panelContents);
|
||||
});
|
||||
|
||||
await test.step('5.Turn off refresh', async () => {
|
||||
const dashboardPage = await gotoDashboardPage({ uid: DASHBOARD_UNDER_TEST });
|
||||
|
||||
const intervalRefreshBtn = dashboardPage.getByGrafanaSelector(
|
||||
selectors.components.RefreshPicker.intervalButtonV2
|
||||
);
|
||||
await intervalRefreshBtn.click();
|
||||
const btn = page.locator('button[aria-label="5 seconds"]');
|
||||
await btn.click();
|
||||
|
||||
const panelContent = dashboardPage.getByGrafanaSelector(selectors.components.Panels.Panel.content).nth(1);
|
||||
const initialPanelContents = await panelContent.textContent();
|
||||
|
||||
await expect(panelContent).not.toHaveText(initialPanelContents!, {
|
||||
timeout: 7000,
|
||||
});
|
||||
|
||||
const refreshedPanelContents = await panelContent.textContent();
|
||||
|
||||
await intervalRefreshBtn.click();
|
||||
const offBtn = page.locator('button[aria-label="Turn off auto refresh"]');
|
||||
await offBtn.click();
|
||||
|
||||
await expect(panelContent).toHaveText(refreshedPanelContents!, {
|
||||
timeout: 7000,
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
);
|
|
@ -0,0 +1,11 @@
|
|||
export const setDashboardUIDs = (uids: string[]) => {
|
||||
process.env.DASHBOARD_UIDS = JSON.stringify(uids);
|
||||
};
|
||||
|
||||
export const getDashboardUIDs = (): string[] => {
|
||||
return JSON.parse(process.env.DASHBOARD_UIDS || '[]');
|
||||
};
|
||||
|
||||
export const clearDashboardUIDs = () => {
|
||||
delete process.env.DASHBOARD_UIDS;
|
||||
};
|
|
@ -0,0 +1,40 @@
|
|||
import { test } from '@playwright/test';
|
||||
|
||||
import cujDashboardOne from '../dashboards/cujs/cuj-dashboard-1.json';
|
||||
import cujDashboardTwo from '../dashboards/cujs/cuj-dashboard-2.json';
|
||||
import cujDashboardThree from '../dashboards/cujs/cuj-dashboard-3.json';
|
||||
|
||||
import { setDashboardUIDs } from './dashboardUidsState';
|
||||
|
||||
const dashboards = [cujDashboardOne, cujDashboardTwo, cujDashboardThree];
|
||||
|
||||
// should be used with live instances that already have dashboards
|
||||
// that match the test dashboard UIDs and format
|
||||
const NO_DASHBOARD_IMPORT = Boolean(process.env.NO_DASHBOARD_IMPORT);
|
||||
|
||||
test.describe('Dashboard CUJS Global Setup', () => {
|
||||
test('import test dashboards', async ({ request }) => {
|
||||
const dashboardUIDs: string[] = [];
|
||||
|
||||
if (NO_DASHBOARD_IMPORT) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Import all test dashboards
|
||||
for (const dashboard of dashboards) {
|
||||
const response = await request.post('/api/dashboards/import', {
|
||||
data: {
|
||||
dashboard,
|
||||
folderUid: '',
|
||||
overwrite: true,
|
||||
inputs: [],
|
||||
},
|
||||
});
|
||||
|
||||
const responseBody = await response.json();
|
||||
dashboardUIDs.push(responseBody.uid);
|
||||
}
|
||||
|
||||
setDashboardUIDs(dashboardUIDs);
|
||||
});
|
||||
});
|
|
@ -0,0 +1,19 @@
|
|||
import { test } from '@playwright/test';
|
||||
|
||||
import { clearDashboardUIDs, getDashboardUIDs } from './dashboardUidsState';
|
||||
|
||||
test.describe('Dashboard CUJS Global Teardown', () => {
|
||||
test('cleanup test dashboards', async ({ request }) => {
|
||||
const dashboardUIDs = getDashboardUIDs();
|
||||
|
||||
if (!dashboardUIDs) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (const dashboardUID of dashboardUIDs) {
|
||||
await request.delete(`/api/dashboards/uid/${dashboardUID}`);
|
||||
}
|
||||
|
||||
clearDashboardUIDs();
|
||||
});
|
||||
});
|
|
@ -0,0 +1,117 @@
|
|||
import { test, expect } from '@grafana/plugin-e2e';
|
||||
|
||||
import {
|
||||
getGroupByInput,
|
||||
getGroupByOptions,
|
||||
getGroupByRestoreButton,
|
||||
getGroupByValues,
|
||||
getMarkdownHTMLContent,
|
||||
} from './cuj-selectors';
|
||||
import { prepareAPIMocks } from './utils';
|
||||
|
||||
test.use({
|
||||
featureToggles: {
|
||||
scopeFilters: true,
|
||||
groupByVariable: true,
|
||||
reloadDashboardsOnParamsChange: true,
|
||||
},
|
||||
});
|
||||
|
||||
export const DASHBOARD_UNDER_TEST = 'cuj-dashboard-1';
|
||||
|
||||
test.describe(
|
||||
'GroupBy CUJs',
|
||||
{
|
||||
tag: ['@dashboard-cujs'],
|
||||
},
|
||||
() => {
|
||||
test('Groupby data on a dashboard', async ({ page, selectors, gotoDashboardPage }) => {
|
||||
prepareAPIMocks(page);
|
||||
const groupByOptions = getGroupByOptions(page);
|
||||
const groupByValues = getGroupByValues(page);
|
||||
const groupByRestoreButton = getGroupByRestoreButton(page);
|
||||
|
||||
await test.step('1.Apply a groupBy across one or mulitple dimensions', async () => {
|
||||
const dashboardPage = await gotoDashboardPage({ uid: DASHBOARD_UNDER_TEST });
|
||||
|
||||
const groupByVariable = getGroupByInput(dashboardPage, selectors);
|
||||
await groupByVariable.click();
|
||||
|
||||
const groupByOption = groupByOptions.nth(1);
|
||||
|
||||
await groupByOption.click();
|
||||
await page.keyboard.press('Escape');
|
||||
|
||||
const selectedValues = await groupByValues.allTextContents();
|
||||
|
||||
// assert the panel is visible and has the correct value
|
||||
const markdownContent = await getMarkdownHTMLContent(dashboardPage, selectors);
|
||||
await expect(markdownContent).toContainText(`GroupByVar: ${selectedValues.join(', ')}`);
|
||||
});
|
||||
|
||||
await test.step('2.Autocomplete for the groupby values', async () => {
|
||||
const dashboardPage = await gotoDashboardPage({ uid: DASHBOARD_UNDER_TEST });
|
||||
|
||||
const groupByVariable = getGroupByInput(dashboardPage, selectors);
|
||||
await expect(groupByVariable).toBeVisible();
|
||||
await groupByVariable.click();
|
||||
|
||||
const groupByOption = groupByOptions.nth(0);
|
||||
const text = await groupByOption.textContent();
|
||||
|
||||
const optionsCount = await groupByOptions.count();
|
||||
|
||||
await groupByVariable.fill(text!);
|
||||
|
||||
const searchedOptionsCount = await groupByOptions.count();
|
||||
|
||||
expect(searchedOptionsCount).toBeLessThanOrEqual(optionsCount);
|
||||
});
|
||||
|
||||
await test.step('3.Edit and restore default groupBy', async () => {
|
||||
const dashboardPage = await gotoDashboardPage({ uid: DASHBOARD_UNDER_TEST });
|
||||
|
||||
const initialSelectedOptionsCount = await groupByValues.count();
|
||||
|
||||
const groupByVariable = getGroupByInput(dashboardPage, selectors);
|
||||
await groupByVariable.click();
|
||||
|
||||
const groupByOption = groupByOptions.nth(1);
|
||||
await groupByOption.click();
|
||||
await page.keyboard.press('Escape');
|
||||
|
||||
const afterEditOptionsCount = await groupByValues.count();
|
||||
|
||||
expect(afterEditOptionsCount).toBe(initialSelectedOptionsCount + 1);
|
||||
|
||||
await groupByRestoreButton.click();
|
||||
|
||||
await expect(groupByValues).not.toHaveCount(afterEditOptionsCount);
|
||||
await expect(groupByValues).toHaveCount(initialSelectedOptionsCount);
|
||||
});
|
||||
|
||||
await test.step('4.Enter multiple values using keyboard only', async () => {
|
||||
const dashboardPage = await gotoDashboardPage({ uid: DASHBOARD_UNDER_TEST });
|
||||
|
||||
const groupByVariable = getGroupByInput(dashboardPage, selectors);
|
||||
await groupByVariable.click();
|
||||
|
||||
const groupByOptionOne = groupByOptions.nth(0);
|
||||
const groupByOptionTwo = groupByOptions.nth(1);
|
||||
const textOne = await groupByOptionOne.textContent();
|
||||
const textTwo = await groupByOptionTwo.textContent();
|
||||
|
||||
await groupByVariable.fill(textOne!);
|
||||
await page.keyboard.press('Enter');
|
||||
|
||||
await groupByVariable.fill(textTwo!);
|
||||
await page.keyboard.press('Enter');
|
||||
|
||||
await page.keyboard.press('Escape');
|
||||
|
||||
await expect(page.getByText(textOne!, { exact: false }).first()).toBeVisible();
|
||||
await expect(page.getByText(textTwo!, { exact: false }).first()).toBeVisible();
|
||||
});
|
||||
});
|
||||
}
|
||||
);
|
|
@ -0,0 +1,181 @@
|
|||
import { test, expect } from '@grafana/plugin-e2e';
|
||||
|
||||
import {
|
||||
applyScopes,
|
||||
expandScopesSelection,
|
||||
getScopeLeafName,
|
||||
getScopeLeafTitle,
|
||||
getScopeTreeName,
|
||||
openScopesSelector,
|
||||
searchScopes,
|
||||
selectScope,
|
||||
TestScope,
|
||||
} from '../utils/scope-helpers';
|
||||
import { testScopes } from '../utils/scopes';
|
||||
|
||||
import { getRecentScopesSelector, getScopesSelectorInput, getScopeTreeCheckboxes } from './cuj-selectors';
|
||||
|
||||
test.use({
|
||||
featureToggles: {
|
||||
scopeFilters: true,
|
||||
groupByVariable: true,
|
||||
reloadDashboardsOnParamsChange: true,
|
||||
},
|
||||
});
|
||||
|
||||
const USE_LIVE_DATA = Boolean(process.env.API_CONFIG_PATH);
|
||||
|
||||
export const DASHBOARD_UNDER_TEST = 'cuj-dashboard-1';
|
||||
|
||||
test.describe(
|
||||
'Scope CUJs',
|
||||
{
|
||||
tag: ['@dashboard-cujs'],
|
||||
},
|
||||
() => {
|
||||
test('Choose a scope', async ({ page, gotoDashboardPage }) => {
|
||||
const scopesSelector = getScopesSelectorInput(page);
|
||||
const recentScopesSelector = getRecentScopesSelector(page);
|
||||
const scopeTreeCheckboxes = getScopeTreeCheckboxes(page);
|
||||
|
||||
await test.step('1.View and select any scope', async () => {
|
||||
await gotoDashboardPage({ uid: DASHBOARD_UNDER_TEST });
|
||||
|
||||
expect.soft(scopesSelector).toHaveValue('');
|
||||
|
||||
const scopes = testScopes();
|
||||
await openScopesSelector(page, USE_LIVE_DATA ? undefined : scopes); //used only in mocked scopes version
|
||||
|
||||
let scopeName = await getScopeTreeName(page, 0);
|
||||
|
||||
const firstLevelScopes = scopes[0].children!; //used only in mocked scopes version
|
||||
await expandScopesSelection(page, scopeName, USE_LIVE_DATA ? undefined : firstLevelScopes);
|
||||
|
||||
scopeName = await getScopeTreeName(page, 1);
|
||||
|
||||
const secondLevelScopes = firstLevelScopes[0].children!; //used only in mocked scopes version
|
||||
await expandScopesSelection(page, scopeName, USE_LIVE_DATA ? undefined : secondLevelScopes);
|
||||
|
||||
const selectedScopes = [secondLevelScopes[0]]; //used only in mocked scopes version
|
||||
|
||||
scopeName = await getScopeLeafName(page, 0);
|
||||
let scopeTitle = await getScopeLeafTitle(page, 0);
|
||||
await selectScope(page, scopeName, USE_LIVE_DATA ? undefined : selectedScopes[0]);
|
||||
|
||||
await applyScopes(page, USE_LIVE_DATA ? undefined : selectedScopes); //used only in mocked scopes version
|
||||
|
||||
expect.soft(scopesSelector).toHaveValue(scopeTitle);
|
||||
});
|
||||
|
||||
await test.step('2.Select a scope across multiple types of production entities', async () => {
|
||||
await gotoDashboardPage({ uid: DASHBOARD_UNDER_TEST });
|
||||
|
||||
expect.soft(scopesSelector).toHaveValue('');
|
||||
|
||||
const scopes = testScopes();
|
||||
await openScopesSelector(page, USE_LIVE_DATA ? undefined : scopes); //used only in mocked scopes version
|
||||
|
||||
let scopeName = await getScopeTreeName(page, 0);
|
||||
|
||||
const firstLevelScopes = scopes[0].children!; //used only in mocked scopes version
|
||||
await expandScopesSelection(page, scopeName, USE_LIVE_DATA ? undefined : firstLevelScopes);
|
||||
|
||||
scopeName = await getScopeTreeName(page, 1);
|
||||
|
||||
const secondLevelScopes = firstLevelScopes[0].children!; //used only in mocked scopes version
|
||||
await expandScopesSelection(page, scopeName, USE_LIVE_DATA ? undefined : secondLevelScopes);
|
||||
|
||||
const scopeTitles: string[] = [];
|
||||
const selectedScopes = [secondLevelScopes[0], secondLevelScopes[1]]; //used only in mocked scopes version
|
||||
for (let i = 0; i < selectedScopes.length; i++) {
|
||||
scopeName = await getScopeLeafName(page, i);
|
||||
scopeTitles.push(await getScopeLeafTitle(page, i));
|
||||
await selectScope(page, scopeName, USE_LIVE_DATA ? undefined : selectedScopes[i]); //used only in mocked scopes version
|
||||
}
|
||||
|
||||
await applyScopes(page, USE_LIVE_DATA ? undefined : selectedScopes); //used only in mocked scopes version
|
||||
|
||||
await expect.soft(scopesSelector).toHaveValue(scopeTitles.join(' + '));
|
||||
});
|
||||
|
||||
await test.step('3.View and select a recently viewed scope', async () => {
|
||||
// this step depends on the previous ones because they set recent scopes
|
||||
await gotoDashboardPage({ uid: DASHBOARD_UNDER_TEST });
|
||||
|
||||
expect.soft(scopesSelector).toHaveValue('');
|
||||
|
||||
const scopes = testScopes();
|
||||
await openScopesSelector(page, USE_LIVE_DATA ? undefined : scopes); //used only in mocked scopes version
|
||||
|
||||
await recentScopesSelector.click();
|
||||
|
||||
const recentScope = recentScopesSelector.locator('../..').locator('button').nth(1);
|
||||
|
||||
const scopeName = await recentScope.locator('span').first().textContent();
|
||||
|
||||
await recentScope.click();
|
||||
|
||||
await expect.soft(scopesSelector).toHaveValue(scopeName!.replace(', ', ' + '));
|
||||
});
|
||||
|
||||
await test.step('4.View and select a scope configured by any team', async () => {
|
||||
await gotoDashboardPage({ uid: DASHBOARD_UNDER_TEST });
|
||||
|
||||
expect.soft(scopesSelector).toHaveValue('');
|
||||
|
||||
const scopes = testScopes();
|
||||
await openScopesSelector(page, USE_LIVE_DATA ? undefined : scopes);
|
||||
|
||||
let scopeName = await getScopeTreeName(page, 1);
|
||||
|
||||
const firstLevelScopes = scopes[2].children!; //used only in mocked scopes version
|
||||
await expandScopesSelection(page, scopeName, USE_LIVE_DATA ? undefined : firstLevelScopes);
|
||||
|
||||
scopeName = await getScopeTreeName(page, 1);
|
||||
|
||||
const secondLevelScopes = firstLevelScopes[0].children!; //used only in mocked scopes version
|
||||
await expandScopesSelection(page, scopeName, USE_LIVE_DATA ? undefined : secondLevelScopes);
|
||||
|
||||
const selectedScopes = [secondLevelScopes[0]]; //used only in mocked scopes version
|
||||
|
||||
scopeName = await getScopeLeafName(page, 0);
|
||||
let scopeTitle = await getScopeLeafTitle(page, 0);
|
||||
await selectScope(page, scopeName, USE_LIVE_DATA ? undefined : selectedScopes[0]);
|
||||
|
||||
await applyScopes(page, USE_LIVE_DATA ? undefined : []); //used only in mocked scopes version
|
||||
|
||||
expect.soft(scopesSelector).toHaveValue(new RegExp(`^${scopeTitle}`));
|
||||
});
|
||||
|
||||
await test.step('5.View pre-completed production entity values as I type', async () => {
|
||||
await gotoDashboardPage({ uid: DASHBOARD_UNDER_TEST });
|
||||
|
||||
const scopes = testScopes();
|
||||
await openScopesSelector(page, USE_LIVE_DATA ? undefined : scopes); //used only in mocked scopes version
|
||||
|
||||
let scopeName = await getScopeTreeName(page, 0);
|
||||
|
||||
const firstLevelScopes = scopes[0].children!; //used only in mocked scopes version
|
||||
await expandScopesSelection(page, scopeName, USE_LIVE_DATA ? undefined : firstLevelScopes);
|
||||
|
||||
scopeName = await getScopeTreeName(page, 1);
|
||||
|
||||
const secondLevelScopes = firstLevelScopes[0].children!; //used only in mocked scopes version
|
||||
await expandScopesSelection(page, scopeName, USE_LIVE_DATA ? undefined : secondLevelScopes);
|
||||
|
||||
const scopeSearchOne = await getScopeLeafTitle(page, 0);
|
||||
const scopeSearchTwo = await getScopeLeafTitle(page, 1);
|
||||
|
||||
await searchScopes(page, scopeSearchOne, [secondLevelScopes[0]]);
|
||||
|
||||
await expect.soft(scopeTreeCheckboxes).toHaveCount(1);
|
||||
expect.soft(await scopeTreeCheckboxes.first().locator('../..').textContent()).toBe(scopeSearchOne);
|
||||
|
||||
await searchScopes(page, scopeSearchTwo, [secondLevelScopes[1]]);
|
||||
|
||||
await expect.soft(scopeTreeCheckboxes).toHaveCount(1);
|
||||
expect.soft(await scopeTreeCheckboxes.first().locator('../..').textContent()).toBe(scopeSearchTwo);
|
||||
});
|
||||
});
|
||||
}
|
||||
);
|
|
@ -0,0 +1,60 @@
|
|||
import { Page } from '@playwright/test';
|
||||
import * as fs from 'fs';
|
||||
import * as path from 'path';
|
||||
|
||||
const USE_LIVE_DATA = Boolean(process.env.API_CONFIG_PATH);
|
||||
const API_CONFIG_PATH = process.env.API_CONFIG_PATH ?? '../dashboards/cujs/config.json';
|
||||
|
||||
async function loadApiConfig() {
|
||||
const configPath = path.resolve(__dirname, API_CONFIG_PATH);
|
||||
|
||||
if (configPath.endsWith('.json')) {
|
||||
const configContent = await fs.promises.readFile(configPath, 'utf-8');
|
||||
return JSON.parse(configContent);
|
||||
} else {
|
||||
const apiConfigModule = await import(configPath);
|
||||
return apiConfigModule.default || apiConfigModule;
|
||||
}
|
||||
}
|
||||
|
||||
export async function prepareAPIMocks(page: Page) {
|
||||
const apiConfig = await loadApiConfig();
|
||||
|
||||
if (USE_LIVE_DATA) {
|
||||
return apiConfig;
|
||||
}
|
||||
|
||||
const keys = Object.keys(apiConfig);
|
||||
|
||||
if (keys.includes('labels')) {
|
||||
// mock the API call to get the labels
|
||||
const labels = ['asserts_env', 'cluster', 'job'];
|
||||
await page.route(apiConfig.labels, async (route) => {
|
||||
await route.fulfill({
|
||||
status: 200,
|
||||
contentType: 'application/json',
|
||||
body: JSON.stringify({
|
||||
status: 'success',
|
||||
data: labels,
|
||||
}),
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
if (keys.includes('values')) {
|
||||
// mock the API call to get the values
|
||||
const values = ['value1', 'value2', 'test1', 'test2'];
|
||||
await page.route(apiConfig.values, async (route) => {
|
||||
await route.fulfill({
|
||||
status: 200,
|
||||
contentType: 'application/json',
|
||||
body: JSON.stringify({
|
||||
status: 'success',
|
||||
data: values,
|
||||
}),
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
return apiConfig;
|
||||
}
|
|
@ -0,0 +1,4 @@
|
|||
{
|
||||
"labels": "**/resources/**/labels*",
|
||||
"values": "**/resources/**/values*"
|
||||
}
|
|
@ -0,0 +1,169 @@
|
|||
{
|
||||
"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,
|
||||
"id": null,
|
||||
"links": [],
|
||||
"panels": [
|
||||
{
|
||||
"gridPos": {
|
||||
"h": 9,
|
||||
"w": 12,
|
||||
"x": 0,
|
||||
"y": 0
|
||||
},
|
||||
"id": 2,
|
||||
"options": {
|
||||
"mode": "markdown",
|
||||
"content": "GroupByVar: $groupBy\n\nAdHocVar: $adHoc\n\nTimerange: ${__url_time_range}"
|
||||
},
|
||||
"pluginVersion": "8.4.0-pre",
|
||||
"title": "Panel Title",
|
||||
"type": "text"
|
||||
},
|
||||
{
|
||||
"datasource": {
|
||||
"type": "datasource",
|
||||
"uid": "grafana"
|
||||
},
|
||||
"fieldConfig": {
|
||||
"defaults": {
|
||||
"color": {
|
||||
"mode": "thresholds"
|
||||
},
|
||||
"custom": {
|
||||
"align": "auto",
|
||||
"cellOptions": {
|
||||
"type": "auto"
|
||||
},
|
||||
"inspect": false
|
||||
},
|
||||
"mappings": [],
|
||||
"thresholds": {
|
||||
"mode": "absolute",
|
||||
"steps": [
|
||||
{
|
||||
"color": "green",
|
||||
"value": 0
|
||||
},
|
||||
{
|
||||
"color": "red",
|
||||
"value": 80
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"overrides": []
|
||||
},
|
||||
"gridPos": {
|
||||
"h": 8,
|
||||
"w": 12,
|
||||
"x": 0,
|
||||
"y": 8
|
||||
},
|
||||
"id": 4,
|
||||
"options": {
|
||||
"cellHeight": "sm",
|
||||
"footer": {
|
||||
"countRows": false,
|
||||
"fields": "",
|
||||
"reducer": ["sum"],
|
||||
"show": false
|
||||
},
|
||||
"showHeader": true
|
||||
},
|
||||
"pluginVersion": "12.2.0-pre",
|
||||
"targets": [
|
||||
{
|
||||
"datasource": {
|
||||
"type": "datasource",
|
||||
"uid": "grafana"
|
||||
},
|
||||
"queryType": "randomWalk",
|
||||
"refId": "A"
|
||||
}
|
||||
],
|
||||
"title": "Table panel",
|
||||
"type": "table"
|
||||
}
|
||||
],
|
||||
"schemaVersion": 41,
|
||||
"tags": [],
|
||||
"templating": {
|
||||
"list": [
|
||||
{
|
||||
"baseFilters": [],
|
||||
"datasource": {
|
||||
"type": "prometheus",
|
||||
"uid": "gdev-prometheus"
|
||||
},
|
||||
"filters": [
|
||||
{
|
||||
"condition": "",
|
||||
"key": "environment",
|
||||
"keyLabel": "environment",
|
||||
"matchAllFilter": false,
|
||||
"operator": "=",
|
||||
"origin": "dashboard",
|
||||
"restorable": false,
|
||||
"value": "prod",
|
||||
"valueLabels": ["prod"],
|
||||
"values": ["prod"]
|
||||
},
|
||||
{
|
||||
"condition": "",
|
||||
"key": "container",
|
||||
"keyLabel": "container",
|
||||
"operator": "=",
|
||||
"value": "test",
|
||||
"valueLabels": ["test"]
|
||||
}
|
||||
],
|
||||
"name": "adHoc",
|
||||
"type": "adhoc"
|
||||
},
|
||||
{
|
||||
"current": {
|
||||
"text": ["job"],
|
||||
"value": ["job"]
|
||||
},
|
||||
"datasource": {
|
||||
"type": "prometheus",
|
||||
"uid": "gdev-prometheus"
|
||||
},
|
||||
"defaultValue": {
|
||||
"text": ["job"],
|
||||
"value": ["job"]
|
||||
},
|
||||
"name": "groupBy",
|
||||
"type": "groupby"
|
||||
}
|
||||
]
|
||||
},
|
||||
"time": {
|
||||
"from": "now-6h",
|
||||
"to": "now"
|
||||
},
|
||||
"timepicker": {},
|
||||
"timezone": "browser",
|
||||
"title": "CUJ Dashboard 1",
|
||||
"uid": "cuj-dashboard-1",
|
||||
"version": 3,
|
||||
"weekStart": ""
|
||||
}
|
|
@ -0,0 +1,86 @@
|
|||
{
|
||||
"annotations": {
|
||||
"list": [
|
||||
{
|
||||
"builtIn": 1,
|
||||
"datasource": {
|
||||
"type": "prometheus",
|
||||
"uid": "gdev-prometheus"
|
||||
},
|
||||
"enable": true,
|
||||
"hide": true,
|
||||
"iconColor": "rgba(0, 211, 255, 1)",
|
||||
"name": "Annotations & Alerts",
|
||||
"type": "dashboard"
|
||||
}
|
||||
]
|
||||
},
|
||||
"editable": true,
|
||||
"fiscalYearStartMonth": 0,
|
||||
"graphTooltip": 0,
|
||||
"id": null,
|
||||
"links": [],
|
||||
"panels": [
|
||||
{
|
||||
"gridPos": {
|
||||
"h": 9,
|
||||
"w": 12,
|
||||
"x": 0,
|
||||
"y": 0
|
||||
},
|
||||
"id": 2,
|
||||
"options": {
|
||||
"mode": "markdown",
|
||||
"content": "GroupByVar: $groupBy\n\nAdHocVar: $adHoc\n\nTimerange: ${__url_time_range}"
|
||||
},
|
||||
"pluginVersion": "8.4.0-pre",
|
||||
"title": "Panel Title",
|
||||
"type": "text"
|
||||
}
|
||||
],
|
||||
"schemaVersion": 41,
|
||||
"tags": [],
|
||||
"templating": {
|
||||
"list": [
|
||||
{
|
||||
"baseFilters": [],
|
||||
"datasource": {
|
||||
"type": "prometheus",
|
||||
"uid": "gdev-prometheus"
|
||||
},
|
||||
"filters": [
|
||||
{
|
||||
"condition": "",
|
||||
"key": "environment",
|
||||
"keyLabel": "environment",
|
||||
"operator": "=",
|
||||
"value": "dev",
|
||||
"valueLabels": ["dev"],
|
||||
"values": ["dev"]
|
||||
}
|
||||
],
|
||||
"name": "adHoc",
|
||||
"type": "adhoc"
|
||||
},
|
||||
{
|
||||
"current": {},
|
||||
"datasource": {
|
||||
"type": "prometheus",
|
||||
"uid": "gdev-prometheus"
|
||||
},
|
||||
"name": "groupBy",
|
||||
"type": "groupby"
|
||||
}
|
||||
]
|
||||
},
|
||||
"time": {
|
||||
"from": "now-6h",
|
||||
"to": "now"
|
||||
},
|
||||
"timepicker": {},
|
||||
"timezone": "browser",
|
||||
"title": "CUJ Dashboard 2",
|
||||
"uid": "cuj-dashboard-2",
|
||||
"version": 3,
|
||||
"weekStart": ""
|
||||
}
|
|
@ -0,0 +1,76 @@
|
|||
{
|
||||
"annotations": {
|
||||
"list": [
|
||||
{
|
||||
"builtIn": 1,
|
||||
"datasource": {
|
||||
"type": "prometheus",
|
||||
"uid": "gdev-prometheus"
|
||||
},
|
||||
"enable": true,
|
||||
"hide": true,
|
||||
"iconColor": "rgba(0, 211, 255, 1)",
|
||||
"name": "Annotations & Alerts",
|
||||
"type": "dashboard"
|
||||
}
|
||||
]
|
||||
},
|
||||
"editable": true,
|
||||
"fiscalYearStartMonth": 0,
|
||||
"graphTooltip": 0,
|
||||
"id": null,
|
||||
"links": [],
|
||||
"panels": [
|
||||
{
|
||||
"gridPos": {
|
||||
"h": 9,
|
||||
"w": 12,
|
||||
"x": 0,
|
||||
"y": 0
|
||||
},
|
||||
"id": 2,
|
||||
"options": {
|
||||
"mode": "markdown",
|
||||
"content": "GroupByVar: $groupBy\n\nAdHocVar: $adHoc\n\nTimerange: ${__url_time_range}"
|
||||
},
|
||||
"pluginVersion": "8.4.0-pre",
|
||||
"title": "Panel Title",
|
||||
"type": "text"
|
||||
}
|
||||
],
|
||||
"schemaVersion": 41,
|
||||
"tags": [],
|
||||
"templating": {
|
||||
"list": [
|
||||
{
|
||||
"baseFilters": [],
|
||||
"datasource": {
|
||||
"type": "prometheus",
|
||||
"uid": "gdev-prometheus"
|
||||
},
|
||||
"filters": [],
|
||||
"name": "adHoc",
|
||||
"type": "adhoc"
|
||||
},
|
||||
{
|
||||
"current": {},
|
||||
"datasource": {
|
||||
"type": "prometheus",
|
||||
"uid": "gdev-prometheus"
|
||||
},
|
||||
"name": "groupBy",
|
||||
"type": "groupby"
|
||||
}
|
||||
]
|
||||
},
|
||||
"time": {
|
||||
"from": "now-6h",
|
||||
"to": "now"
|
||||
},
|
||||
"timepicker": {},
|
||||
"timezone": "browser",
|
||||
"title": "CUJ Dashboard 3",
|
||||
"uid": "cuj-dashboard-3",
|
||||
"version": 3,
|
||||
"weekStart": ""
|
||||
}
|
|
@ -0,0 +1,295 @@
|
|||
import { Page, Response } from '@playwright/test';
|
||||
|
||||
import { ScopeDashboardBindingSpec, ScopeDashboardBindingStatus } from '@grafana/data';
|
||||
|
||||
import { Resource } from '../../public/app/features/apiserver/types';
|
||||
|
||||
import { testScopes } from './scopes';
|
||||
|
||||
const USE_LIVE_DATA = Boolean(process.env.API_CALLS_CONFIG_PATH);
|
||||
|
||||
export type TestScope = {
|
||||
name: string;
|
||||
title: string;
|
||||
children?: TestScope[];
|
||||
filters?: Array<{ key: string; value: string; operator: string }>;
|
||||
dashboardUid?: string;
|
||||
dashboardTitle?: string;
|
||||
disableMultiSelect?: boolean;
|
||||
type?: string;
|
||||
category?: string;
|
||||
addLinks?: boolean;
|
||||
};
|
||||
|
||||
type ScopeDashboardBinding = Resource<ScopeDashboardBindingSpec, ScopeDashboardBindingStatus, 'ScopeDashboardBinding'>;
|
||||
|
||||
export async function scopeNodeChildrenRequest(
|
||||
page: Page,
|
||||
scopes: TestScope[],
|
||||
parentName?: string
|
||||
): Promise<Response> {
|
||||
await page.route(`**/apis/scope.grafana.app/v0alpha1/namespaces/*/find/scope_node_children*`, async (route) => {
|
||||
await route.fulfill({
|
||||
status: 200,
|
||||
contentType: 'application/json',
|
||||
body: JSON.stringify({
|
||||
apiVersion: 'scope.grafana.app/v0alpha1',
|
||||
kind: 'FindScopeNodeChildrenResults',
|
||||
metadata: {},
|
||||
items: scopes.map((scope) => ({
|
||||
kind: 'ScopeNode',
|
||||
apiVersion: 'scope.grafana.app/v0alpha1',
|
||||
metadata: {
|
||||
name: `${scope.name}`,
|
||||
namespace: 'default',
|
||||
},
|
||||
spec: {
|
||||
title: scope.title,
|
||||
description: scope.title,
|
||||
disableMultiSelect: scope.disableMultiSelect ?? false,
|
||||
nodeType: scope.children ? 'container' : 'leaf',
|
||||
...(parentName && {
|
||||
parentName,
|
||||
}),
|
||||
...((scope.addLinks || scope.children) && {
|
||||
linkType: 'scope',
|
||||
linkId: `scope-${scope.name}`,
|
||||
}),
|
||||
},
|
||||
})),
|
||||
}),
|
||||
});
|
||||
});
|
||||
|
||||
return page.waitForResponse((response) => response.url().includes(`/find/scope_node_children`));
|
||||
}
|
||||
|
||||
export async function openScopesSelector(page: Page, scopes?: TestScope[]) {
|
||||
const click = async () => await page.getByTestId('scopes-selector-input').click();
|
||||
|
||||
if (!scopes) {
|
||||
await click();
|
||||
return;
|
||||
}
|
||||
|
||||
const responsePromise = scopeNodeChildrenRequest(page, scopes);
|
||||
|
||||
await click();
|
||||
await responsePromise;
|
||||
}
|
||||
|
||||
export async function expandScopesSelection(page: Page, parentScope: string, scopes?: TestScope[]) {
|
||||
const click = async () => await page.getByTestId(`scopes-tree-${parentScope}-expand`).click();
|
||||
|
||||
if (!scopes) {
|
||||
await click();
|
||||
return;
|
||||
}
|
||||
|
||||
const responsePromise = scopeNodeChildrenRequest(page, scopes, parentScope);
|
||||
|
||||
await click();
|
||||
await responsePromise;
|
||||
}
|
||||
|
||||
export async function scopeSelectRequest(page: Page, selectedScope: TestScope): Promise<Response> {
|
||||
await page.route(
|
||||
`**/apis/scope.grafana.app/v0alpha1/namespaces/*/scopes/scope-${selectedScope.name}`,
|
||||
async (route) => {
|
||||
await route.fulfill({
|
||||
status: 200,
|
||||
contentType: 'application/json',
|
||||
body: JSON.stringify({
|
||||
kind: 'Scope',
|
||||
apiVersion: 'scope.grafana.app/v0alpha1',
|
||||
metadata: {
|
||||
name: `scope-${selectedScope.name}`,
|
||||
namespace: 'default',
|
||||
},
|
||||
spec: {
|
||||
title: selectedScope.title,
|
||||
description: '',
|
||||
filters: selectedScope.filters,
|
||||
category: selectedScope.category,
|
||||
type: selectedScope.type,
|
||||
},
|
||||
}),
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
return page.waitForResponse((response) => response.url().includes(`/scopes/scope-${selectedScope.name}`));
|
||||
}
|
||||
|
||||
export async function selectScope(page: Page, scopeName: string, selectedScope?: TestScope) {
|
||||
const click = async () => {
|
||||
const element = page.locator(
|
||||
`[data-testid="scopes-tree-${scopeName}-checkbox"], [data-testid="scopes-tree-${scopeName}-radio"]`
|
||||
);
|
||||
await element.scrollIntoViewIfNeeded();
|
||||
await element.click({ force: true });
|
||||
};
|
||||
|
||||
if (!selectedScope) {
|
||||
await click();
|
||||
return;
|
||||
}
|
||||
|
||||
const responsePromise = scopeSelectRequest(page, selectedScope);
|
||||
|
||||
await click();
|
||||
await responsePromise;
|
||||
}
|
||||
|
||||
export async function applyScopes(page: Page, scopes?: TestScope[]) {
|
||||
const click = async () => {
|
||||
await page.getByTestId('scopes-selector-apply').scrollIntoViewIfNeeded();
|
||||
await page.getByTestId('scopes-selector-apply').click({ force: true });
|
||||
};
|
||||
|
||||
if (!scopes) {
|
||||
await click();
|
||||
return;
|
||||
}
|
||||
|
||||
const url: string =
|
||||
'**/apis/scope.grafana.app/v0alpha1/namespaces/*/find/scope_dashboard_bindings?' +
|
||||
scopes.map((scope) => `scope=scope-${scope.name}`).join('&');
|
||||
|
||||
const groups: string[] = ['Most relevant', 'Dashboards', 'Something else', ''];
|
||||
|
||||
await page.route(url, async (route) => {
|
||||
await route.fulfill({
|
||||
status: 200,
|
||||
contentType: 'application/json',
|
||||
body: JSON.stringify({
|
||||
apiVersion: 'scope.grafana.app/v0alpha1',
|
||||
items: scopes.flatMap((scope) => {
|
||||
const bindings: ScopeDashboardBinding[] = [];
|
||||
|
||||
for (let i = 0; i < 10; i++) {
|
||||
const selectedGroup = groups[Math.floor(Math.random() * groups.length)];
|
||||
bindings.push({
|
||||
kind: 'ScopeDashboardBinding',
|
||||
apiVersion: 'scope.grafana.app/v0alpha1',
|
||||
metadata: {
|
||||
name: 'scope',
|
||||
resourceVersion: '1',
|
||||
creationTimestamp: 'stamp',
|
||||
},
|
||||
spec: {
|
||||
dashboard: (scope.dashboardUid ?? 'edediimbjhdz4b') + '/' + Math.random().toString(),
|
||||
scope: `scope-${scope.name}`,
|
||||
},
|
||||
status: {
|
||||
dashboardTitle: (scope.dashboardTitle ?? 'A tall dashboard') + (selectedGroup[0] ?? 'U') + i,
|
||||
...(selectedGroup !== '' && { groups: [selectedGroup] }),
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
// make sure there is always a binding with no group
|
||||
bindings.push({
|
||||
kind: 'ScopeDashboardBinding',
|
||||
apiVersion: 'scope.grafana.app/v0alpha1',
|
||||
metadata: {
|
||||
name: 'scope',
|
||||
resourceVersion: '1',
|
||||
creationTimestamp: 'stamp',
|
||||
},
|
||||
spec: {
|
||||
dashboard: (scope.dashboardUid ?? 'edediimbjhdz4b') + '/' + Math.random().toString(),
|
||||
scope: `scope-${scope.name}`,
|
||||
},
|
||||
status: {
|
||||
dashboardTitle: (scope.dashboardTitle ?? 'A tall dashboard') + 'U123',
|
||||
},
|
||||
});
|
||||
return bindings;
|
||||
}),
|
||||
}),
|
||||
});
|
||||
});
|
||||
|
||||
const responsePromise = page.waitForResponse((response) => response.url().includes(`/find/scope_dashboard_bindings`));
|
||||
const scopeRequestPromises: Array<Promise<Response>> = [];
|
||||
|
||||
for (const scope of scopes) {
|
||||
scopeRequestPromises.push(scopeSelectRequest(page, scope));
|
||||
}
|
||||
|
||||
await click();
|
||||
await responsePromise;
|
||||
await Promise.all(scopeRequestPromises);
|
||||
}
|
||||
|
||||
export async function searchScopes(page: Page, value: string, resultScopes: TestScope[]) {
|
||||
const click = async () => await page.getByTestId('scopes-tree-search').fill(value);
|
||||
|
||||
if (!resultScopes) {
|
||||
await click();
|
||||
return;
|
||||
}
|
||||
|
||||
const responsePromise = scopeNodeChildrenRequest(page, resultScopes);
|
||||
|
||||
await click();
|
||||
await responsePromise;
|
||||
}
|
||||
|
||||
export async function getScopeTreeName(page: Page, nth: number): Promise<string> {
|
||||
const locator = page.getByTestId(/^scopes-tree-.*-expand/).nth(nth);
|
||||
const fullTestId = await locator.getAttribute('data-testid');
|
||||
const scopeName = fullTestId?.replace(/^scopes-tree-/, '').replace(/-expand$/, '');
|
||||
|
||||
if (!scopeName) {
|
||||
throw new Error('There are no scopes in the selector');
|
||||
}
|
||||
|
||||
return scopeName;
|
||||
}
|
||||
|
||||
export async function getScopeLeafName(page: Page, nth: number): Promise<string> {
|
||||
const locator = page.getByTestId(/^scopes-tree-.*-(checkbox|radio)/).nth(nth);
|
||||
const fullTestId = await locator.getAttribute('data-testid');
|
||||
const scopeName = fullTestId?.replace(/^scopes-tree-/, '').replace(/-(checkbox|radio)/, '');
|
||||
|
||||
if (!scopeName) {
|
||||
throw new Error('There are no scopes in the selector');
|
||||
}
|
||||
|
||||
return scopeName;
|
||||
}
|
||||
|
||||
export async function getScopeLeafTitle(page: Page, nth: number): Promise<string> {
|
||||
const locator = page.getByTestId(/^scopes-tree-.*-(checkbox|radio)/).nth(nth);
|
||||
const scopeTitle = await locator.locator('../..').textContent();
|
||||
|
||||
if (!scopeTitle) {
|
||||
throw new Error('There are no scopes in the selector');
|
||||
}
|
||||
|
||||
return scopeTitle;
|
||||
}
|
||||
|
||||
export async function setScopes(page: Page, scopeBindingSetting?: { uid: string; title: string }) {
|
||||
const scopes = testScopes(scopeBindingSetting);
|
||||
await openScopesSelector(page, USE_LIVE_DATA ? undefined : scopes); //used only in mocked scopes version
|
||||
|
||||
let scopeName = await getScopeTreeName(page, 0);
|
||||
|
||||
const firstLevelScopes = scopes[0].children!; //used only in mocked scopes version
|
||||
await expandScopesSelection(page, scopeName, USE_LIVE_DATA ? undefined : firstLevelScopes);
|
||||
|
||||
scopeName = await getScopeTreeName(page, 1);
|
||||
|
||||
const secondLevelScopes = firstLevelScopes[0].children!; //used only in mocked scopes version
|
||||
await expandScopesSelection(page, scopeName, USE_LIVE_DATA ? undefined : secondLevelScopes);
|
||||
|
||||
const selectedScopes = [secondLevelScopes[0]]; //used only in mocked scopes version
|
||||
|
||||
scopeName = await getScopeLeafName(page, 0);
|
||||
await selectScope(page, scopeName, USE_LIVE_DATA ? undefined : selectedScopes[0]);
|
||||
|
||||
await applyScopes(page, USE_LIVE_DATA ? undefined : selectedScopes); //used only in mocked scopes version
|
||||
}
|
|
@ -0,0 +1,107 @@
|
|||
import { TestScope } from './scope-helpers';
|
||||
|
||||
export const testScopes = (scopeBindingSetting?: { uid: string; title: string }): TestScope[] => {
|
||||
return [
|
||||
{
|
||||
name: 'sn-databases',
|
||||
title: 'Databases',
|
||||
children: [
|
||||
{
|
||||
name: 'sn-databases-m',
|
||||
title: 'Mimir',
|
||||
children: [
|
||||
{
|
||||
name: 'sn-databases-m-mimir-dev-10',
|
||||
title: 'mimir-dev-10',
|
||||
filters: [{ key: 'namespace', operator: 'equals', value: 'mimir-dev-10' }],
|
||||
dashboardTitle: scopeBindingSetting?.title ?? 'CUJ Dashboard 2',
|
||||
dashboardUid: scopeBindingSetting?.uid ?? 'cuj-dashboard-2',
|
||||
type: 'type',
|
||||
category: 'category',
|
||||
addLinks: true,
|
||||
},
|
||||
{
|
||||
name: 'sn-databases-m-mimir-dev-11',
|
||||
title: 'mimir-dev-11',
|
||||
filters: [{ key: 'namespace', operator: 'equals', value: 'mimir-dev-11' }],
|
||||
category: 'category',
|
||||
type: 'type',
|
||||
addLinks: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'sn-databases-l',
|
||||
title: 'Loki',
|
||||
children: [
|
||||
{
|
||||
name: 'sn-databases-l-loki-dev-010',
|
||||
title: 'loki-dev-010',
|
||||
filters: [{ key: 'namespace', operator: 'equals', value: 'loki-dev-010' }],
|
||||
dashboardTitle: scopeBindingSetting?.title ?? 'CUJ Dashboard 2',
|
||||
dashboardUid: scopeBindingSetting?.uid ?? 'cuj-dashboard-2',
|
||||
addLinks: true,
|
||||
},
|
||||
{
|
||||
name: 'sn-databases-l-loki-dev-009',
|
||||
title: 'loki-dev-009',
|
||||
filters: [{ key: 'namespace', operator: 'equals', value: 'loki-dev-009' }],
|
||||
addLinks: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'sn-hg',
|
||||
title: 'Hosted Grafana',
|
||||
children: [
|
||||
{
|
||||
name: 'sn-hg-c',
|
||||
title: 'Cluster',
|
||||
children: [
|
||||
{
|
||||
name: 'sn-hg-c-dev-eu-west-2-hosted-grafana',
|
||||
title: 'dev-eu-west-2',
|
||||
filters: [{ key: 'cluster', operator: 'equals', value: 'dev-eu-west-2' }],
|
||||
dashboardTitle: scopeBindingSetting?.title ?? 'CUJ Dashboard 2',
|
||||
dashboardUid: scopeBindingSetting?.uid ?? 'cuj-dashboard-2',
|
||||
addLinks: true,
|
||||
},
|
||||
{
|
||||
name: 'sn-hg-c-dev-us-central-0-hosted-grafana',
|
||||
title: 'dev-us-central-0',
|
||||
filters: [{ key: 'cluster', operator: 'equals', value: 'dev-us-central-0' }],
|
||||
addLinks: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'sn-other-teams',
|
||||
title: 'Other teams',
|
||||
children: [
|
||||
{
|
||||
name: 'sn-other-teams-t',
|
||||
title: 'Test',
|
||||
disableMultiSelect: true,
|
||||
children: [
|
||||
{
|
||||
name: 'sn-other-teams-t-multi',
|
||||
title: 'Multi group',
|
||||
children: [],
|
||||
addLinks: true,
|
||||
},
|
||||
{
|
||||
name: 'sn-other-teams-t-another',
|
||||
title: 'Another group',
|
||||
addLinks: true,
|
||||
filters: [],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
};
|
|
@ -593,6 +593,7 @@ export {
|
|||
} from './types/pluginExtensions';
|
||||
export {
|
||||
type ScopeDashboardBindingSpec,
|
||||
type ScopeDashboardBindingStatus,
|
||||
type ScopeDashboardBinding,
|
||||
type ScopeFilterOperator,
|
||||
type ScopeSpecFilter,
|
||||
|
|
|
@ -183,5 +183,25 @@ export default defineConfig<PluginOptions>({
|
|||
name: 'dashboard-new-layouts',
|
||||
testDir: path.join(testDirRoot, '/dashboard-new-layouts'),
|
||||
}),
|
||||
// Setup project for dashboard CUJS tests
|
||||
withAuth({
|
||||
name: 'dashboard-cujs-setup',
|
||||
testDir: path.join(testDirRoot, '/dashboard-cujs'),
|
||||
testMatch: ['global-setup.spec.ts'],
|
||||
}),
|
||||
// Main dashboard CUJS tests
|
||||
withAuth({
|
||||
name: 'dashboard-cujs',
|
||||
testDir: path.join(testDirRoot, '/dashboard-cujs'),
|
||||
testIgnore: ['global-setup.spec.ts', 'global-teardown.spec.ts'],
|
||||
dependencies: ['dashboard-cujs-setup'],
|
||||
}),
|
||||
// Teardown project for dashboard CUJS tests
|
||||
withAuth({
|
||||
name: 'dashboard-cujs-teardown',
|
||||
testDir: path.join(testDirRoot, '/dashboard-cujs'),
|
||||
testMatch: ['global-teardown.spec.ts'],
|
||||
dependencies: ['dashboard-cujs'],
|
||||
}),
|
||||
],
|
||||
});
|
||||
|
|
Loading…
Reference in New Issue