import { Page } from '@playwright/test'; import { selectors } from '@grafana/e2e-selectors'; import { DashboardPage, E2ESelectorGroups, expect } from '@grafana/plugin-e2e'; import testV2Dashboard from '../dashboards/TestV2Dashboard.json'; const deselectPanels = async (dashboardPage: DashboardPage, selectors: E2ESelectorGroups) => { await dashboardPage.getByGrafanaSelector(selectors.pages.Dashboard.Controls).click({ position: { x: 0, y: 0 }, }); }; export const flows = { deselectPanels, async changePanelTitle( dashboardPage: DashboardPage, selectors: E2ESelectorGroups, oldPanelTitle: string, newPanelTitle: string ) { await deselectPanels(dashboardPage, selectors); await dashboardPage .getByGrafanaSelector(selectors.components.Panels.Panel.headerContainer) .filter({ hasText: oldPanelTitle }) .first() .click(); await dashboardPage .getByGrafanaSelector(selectors.components.PanelEditor.OptionsPane.fieldInput('Title')) .fill(newPanelTitle); }, async changePanelDescription( dashboardPage: DashboardPage, selectors: E2ESelectorGroups, panelTitle: string, newDescription: string ) { await deselectPanels(dashboardPage, selectors); const panelTitleRegex = new RegExp(`^${panelTitle}$`); await dashboardPage .getByGrafanaSelector(selectors.components.Panels.Panel.headerContainer) .filter({ hasText: panelTitleRegex }) .first() .click(); const descriptionTextArea = dashboardPage .getByGrafanaSelector(selectors.components.PanelEditor.OptionsPane.fieldLabel('panel-options Description')) .locator('textarea'); await descriptionTextArea.fill(newDescription); }, async newEditPaneVariableClick(dashboardPage: DashboardPage, selectors: E2ESelectorGroups) { await dashboardPage.getByGrafanaSelector(selectors.components.NavToolbar.editDashboard.editButton).click(); await dashboardPage.getByGrafanaSelector(selectors.pages.Dashboard.Sidebar.outlineButton).click(); await dashboardPage.getByGrafanaSelector(selectors.components.PanelEditor.Outline.item('Variables')).click(); await dashboardPage.getByGrafanaSelector(selectors.components.Sidebar.dockToggle).click(); await dashboardPage .getByGrafanaSelector(selectors.components.PanelEditor.ElementEditPane.addVariableButton) .click(); }, async newEditPanelCommonVariableInputs( dashboardPage: DashboardPage, selectors: E2ESelectorGroups, variable: Variable ) { await dashboardPage .getByGrafanaSelector(selectors.components.PanelEditor.ElementEditPane.variableType(variable.type)) .click(); const variableNameInput = dashboardPage.getByGrafanaSelector( selectors.components.PanelEditor.ElementEditPane.variableNameInput ); await variableNameInput.click(); await variableNameInput.fill(variable.name); await variableNameInput.blur(); if (variable.label) { const variableLabelInput = dashboardPage.getByGrafanaSelector( selectors.components.PanelEditor.ElementEditPane.variableLabelInput ); await variableLabelInput.click(); await variableLabelInput.fill(variable.label); await variableLabelInput.blur(); } }, async addNewTextBoxVariable(dashboardPage: DashboardPage, variable: Variable) { await flows.newEditPaneVariableClick(dashboardPage, selectors); await flows.newEditPanelCommonVariableInputs(dashboardPage, selectors, variable); // set the textbox variable value const type = 'variable-type Value'; const fieldLabel = dashboardPage.getByGrafanaSelector( selectors.components.PanelEditor.OptionsPane.fieldLabel(type) ); await expect(fieldLabel).toBeVisible(); const inputField = fieldLabel.locator('input'); await expect(inputField).toBeVisible(); await inputField.fill(variable.value); await inputField.blur(); }, }; export type Variable = { type: string; name: string; label?: string; description?: string; value: string; }; export async function saveDashboard( dashboardPage: DashboardPage, page: Page, selectors: E2ESelectorGroups, title?: string ) { await dashboardPage.getByGrafanaSelector(selectors.components.NavToolbar.editDashboard.saveButton).click(); if (title) { await page.getByTestId(selectors.components.Drawer.DashboardSaveDrawer.saveAsTitleInput).fill(title); } await dashboardPage.getByGrafanaSelector(selectors.components.Drawer.DashboardSaveDrawer.saveButton).click(); await expect(page.getByText('Dashboard saved')).toBeVisible(); } export async function checkRepeatedPanelTitles( dashboardPage: DashboardPage, selectors: E2ESelectorGroups, title: string, options: Array, expectHidden = false ) { for (const option of options) { const titleLocator = dashboardPage.getByGrafanaSelector( selectors.components.Panels.Panel.title(`${title}${option}`) ); if (expectHidden) { await expect(titleLocator).toBeHidden(); } else { await expect(titleLocator).toBeVisible(); } } } export async function movePanel( dashboardPage: DashboardPage, selectors: E2ESelectorGroups, sourcePanel: string | RegExp, targetPanel: string | RegExp ) { // Get target panel position const targetPanelElement = dashboardPage .getByGrafanaSelector(selectors.components.Panels.Panel.headerContainer) .filter({ hasText: targetPanel }) .first(); // Get source panel element const sourcePanelElement = dashboardPage .getByGrafanaSelector(selectors.components.Panels.Panel.headerContainer) .filter({ hasText: sourcePanel }); // Perform drag and drop await sourcePanelElement.dragTo(targetPanelElement); } export async function getPanelPosition( dashboardPage: DashboardPage, selectors: E2ESelectorGroups, panelTitle: string | RegExp ) { const panel = dashboardPage .getByGrafanaSelector(selectors.components.Panels.Panel.headerContainer) .filter({ hasText: panelTitle }) .first(); const boundingBox = await panel.boundingBox(); return boundingBox; } export async function verifyChanges( dashboardPage: DashboardPage, page: Page, selectors: E2ESelectorGroups, changeText: string ) { await dashboardPage.getByGrafanaSelector(selectors.components.NavToolbar.editDashboard.saveButton).click(); await dashboardPage.getByGrafanaSelector(selectors.components.Tab.title('Changes')).click(); await expect(page.getByText('Full JSON diff').locator('..')).toContainText(changeText); await dashboardPage.getByGrafanaSelector(selectors.components.Drawer.General.close).click(); } export async function importTestDashboard(page: Page, selectors: E2ESelectorGroups, title: string, dashInput?: string) { await page.goto(selectors.pages.ImportDashboard.url); await page .getByTestId(selectors.components.DashboardImportPage.textarea) .fill(dashInput || JSON.stringify(testV2Dashboard)); await page.getByTestId(selectors.components.DashboardImportPage.submit).click(); await page.getByTestId(selectors.components.ImportDashboardForm.name).fill(title); await page.getByTestId(selectors.components.DataSourcePicker.inputV2).click(); await page.locator('div[data-testid="data-source-card"]').first().click(); await page.getByTestId(selectors.components.ImportDashboardForm.submit).click(); const undockMenuButton = page.locator('[aria-label="Undock menu"]'); const undockMenuVisible = await undockMenuButton.isVisible(); if (undockMenuVisible) { undockMenuButton.click(); } await expect(page.locator('[data-testid="uplot-main-div"]').first()).toBeVisible(); } export async function goToEmbeddedPanel(page: Page) { // extracting embedded panel url from UI const textAreaValue = await page.getByTestId('share-embed-html').evaluate((el) => el.textContent); const srcRegex = /src="([^"]*)"/; let soloPanelUrl = textAreaValue.match(srcRegex)?.[1]; expect(soloPanelUrl).toBeDefined(); // adjust base url (different each time in CI) const currentUrl = page.url(); const baseUrlRegex = /^http:\/\/[^/:]+:3001\//; const baseUrl = currentUrl.match(baseUrlRegex)?.[0]; soloPanelUrl = soloPanelUrl!.replace(baseUrlRegex, baseUrl!); await page.goto(soloPanelUrl!); } export async function goToPanelSnapshot(page: Page) { // extracting snapshot url from clipboard const snapshotUrl = await page.evaluate(() => navigator.clipboard.readText()); expect(snapshotUrl).toBeDefined(); await page.goto(snapshotUrl); } export async function moveTab( dashboardPage: DashboardPage, page: Page, selectors: E2ESelectorGroups, sourceTab: string, targetTab: string ) { // Get target panel position const targetTabElement = dashboardPage.getByGrafanaSelector(selectors.components.Tab.title(targetTab)).first(); // Get source panel element const sourceTabElement = dashboardPage.getByGrafanaSelector(selectors.components.Tab.title(sourceTab)).first(); const targetBox = await targetTabElement.boundingBox(); // Perform drag and drop (dragTo() did not work in this case) await sourceTabElement.hover(); await page.mouse.down(); // move to adjusted target position (relative to top left) await page.mouse.move((targetBox?.x || 0) + (targetBox?.width || 0), targetBox?.y || 0, { steps: 5 }); await page.mouse.up(); } export async function groupIntoTab(page: Page, dashboardPage: DashboardPage, selectors: E2ESelectorGroups) { await dashboardPage.getByGrafanaSelector(selectors.components.CanvasGridAddActions.groupPanels).click(); await page.getByText('Group into tab').click(); } export async function checkRepeatedTabTitles( dashboardPage: DashboardPage, selectors: E2ESelectorGroups, title: string, options: Array ) { for (const option of options) { await expect(dashboardPage.getByGrafanaSelector(selectors.components.Tab.title(`${title}${option}`))).toBeVisible(); } } export async function getTabPosition(dashboardPage: DashboardPage, selectors: E2ESelectorGroups, tabTitle: string) { const tab = dashboardPage.getByGrafanaSelector(selectors.components.Tab.title(tabTitle)).first(); const boundingBox = await tab.boundingBox(); return boundingBox; } export async function switchToAutoGrid(page: Page, dashboardPage: DashboardPage) { await page.getByLabel('layout-selection-option-Auto grid').click(); // confirm layout change if applicable const confirmModal = dashboardPage.getByGrafanaSelector(selectors.pages.ConfirmModal.delete); if (confirmModal) { await confirmModal.click(); } }