diff --git a/e2e/plugin-e2e/canvas/canvas-scene.spec.ts b/e2e/plugin-e2e/canvas/canvas-scene.spec.ts new file mode 100644 index 00000000000..b640f450223 --- /dev/null +++ b/e2e/plugin-e2e/canvas/canvas-scene.spec.ts @@ -0,0 +1,87 @@ +import { Locator } from '@playwright/test'; + +import { test, expect } from '@grafana/plugin-e2e'; + +test.use({ + featureToggles: { + canvasPanelPanZoom: true, + }, +}); + +test.describe('Canvas Panel - Scene Tests', () => { + test.beforeEach(async ({ page, gotoDashboardPage }) => { + const dashboardPage = await gotoDashboardPage({}); + const panelEditPage = await dashboardPage.addPanel(); + await panelEditPage.setVisualization('Canvas'); + + // Wait for canvas panel to load + await page.waitForSelector('[data-testid="canvas-scene-pan-zoom"]', { timeout: 10000 }); + }); + + test('should create and render canvas panel with scene elements', async ({ page }) => { + const canvasElement = await page.getByRole('button', { name: 'Double click to set field' }); + await expect(canvasElement).toBeVisible(); + }); + + test('should handle scene pan and zoom when enabled', async ({ page }) => { + // Feature toggle is enabled, pan/zoom functionality should be available + const panZoomCheckbox = await page.getByLabel('Canvas Pan and zoom field').locator('label').nth(1); + await panZoomCheckbox.setChecked(true); + await expect(panZoomCheckbox).toBeChecked({ checked: true }); + + const canvasElement = await page.getByRole('button', { name: 'Double click to set field' }); + const canvasSceneWrapper = await page.getByTestId('canvas-scene-wrapper'); + + // Check if infinite viewer is present (pan/zoom feature) + await page.waitForSelector('[data-testid="canvas-scene-pan-zoom"]', { timeout: 10000 }); + const infiniteViewer = page.locator('[data-testid="canvas-scene-pan-zoom"]'); + await infiniteViewer.waitFor({ state: 'visible', timeout: 5000 }); + await expect(await infiniteViewer.isVisible()).toBe(true); + await infiniteViewer.hover(); + + const viewerBounds = await infiniteViewer.boundingBox(); + await expect(viewerBounds).toBeDefined(); + + // Test pan functionality + const startX = viewerBounds.x + 50; + const startY = viewerBounds.y + 50; + const endX = viewerBounds.x + 250; + const endY = viewerBounds.y + 250; + await page.getByTestId('canvas-scene-pan-zoom'); + await page.mouse.move(startX, startY); + await page.mouse.down({ button: 'middle' }); + await page.mouse.move(endX, endY); + await page.mouse.up({ button: 'middle' }); + await expect(await isOutsideViewport(canvasElement, canvasSceneWrapper)).toBe(true); + + // Test zoom reset with double-click + await page.mouse.dblclick(startX, startY); + // Verify canvas element is visible after pan/zoom operations + await expect(await isOutsideViewport(canvasElement, canvasSceneWrapper)).toBe(false); + + // Test zoom functionality + await page.mouse.move(startX, startY); + await page.keyboard.down('Control'); + await page.mouse.wheel(0, -400); // Zoom in + await page.keyboard.up('Control'); + // Check if canvas element is not visible after zoom operations + await expect(await isOutsideViewport(canvasElement, canvasSceneWrapper)).toBe(true); + + // Test zoom reset with double-click + await page.mouse.dblclick(startX, startY); + // Verify canvas element is visible after pan/zoom operations + await expect(await isOutsideViewport(canvasElement, canvasSceneWrapper)).toBe(false); + }); +}); + +// TODO: this function is workaround for .toBeVisible() +async function isOutsideViewport(element: Locator, viewPort: Locator): Promise { + const elementBounds = await element.boundingBox(); + const viewportBounds = await viewPort.boundingBox(); + return ( + elementBounds.x + elementBounds.width < viewportBounds.x || + elementBounds.x > viewportBounds.x + viewportBounds.width || + elementBounds.y + elementBounds.height < viewportBounds.y || + elementBounds.y > viewportBounds.y + viewportBounds.height + ); +} diff --git a/playwright.config.ts b/playwright.config.ts index 39ac6aebd5f..8185520c89e 100644 --- a/playwright.config.ts +++ b/playwright.config.ts @@ -178,6 +178,15 @@ export default defineConfig({ }, dependencies: ['authenticate'], }, + { + name: 'canvas', + testDir: path.join(testDirRoot, '/canvas'), + use: { + ...devices['Desktop Chrome'], + storageState: 'playwright/.auth/admin.json', + }, + dependencies: ['authenticate'], + }, { name: 'zipkin', testDir: path.join(testDirRoot, '/zipkin'), diff --git a/public/app/features/canvas/runtime/scene.tsx b/public/app/features/canvas/runtime/scene.tsx index 16174e1356f..7d93f127f58 100644 --- a/public/app/features/canvas/runtime/scene.tsx +++ b/public/app/features/canvas/runtime/scene.tsx @@ -386,13 +386,24 @@ export class Scene { ); return config.featureToggles.canvasPanelPanZoom ? ( -
-
+
+
{sceneDiv}
) : ( -
+
{sceneDiv}
); diff --git a/public/locales/en-US/grafana.json b/public/locales/en-US/grafana.json index ab3d742f57d..c21b183eb32 100644 --- a/public/locales/en-US/grafana.json +++ b/public/locales/en-US/grafana.json @@ -3693,7 +3693,6 @@ } }, "description-experimental-types": "Enable selection of experimental element types", - "description-infinite-panning": "Enable infinite panning - useful for expansive canvases. Warning: this is an experimental feature and currently only works well with elements that are top / left constrained", "description-inline-editing": "Enable editing the panel directly", "description-pan-zoom": "Enable pan and zoom", "direction-options": { @@ -3794,7 +3793,6 @@ "name-align-text": "Align text", "name-color": "Text color", "name-experimental-types": "Experimental element types", - "name-infinite-panning": "Infinite panning", "name-inline-editing": "Inline editing", "name-pan-zoom": "Pan and zoom", "name-text": "Text",