Files
grafana/e2e-playwright/various-suite/keybinds.spec.ts
T
Ivan Ortega Alba c0ce4ff1f2 Dashboards: Fix missing Ctrl+O keyboard shortcut for crosshair toggle (#111310)
* Dashboard Scenes: Fix missing Ctrl+O keyboard shortcut for crosshair toggle

- Add missing mod+o keybind to dashboard scenes keyboard shortcuts
- Implement crosshair state cycling (Default -> Crosshair -> Tooltip -> Default)
- Add comprehensive unit tests for keyboard shortcuts functionality
- Add e2e test to verify shortcut works and prevents browser file dialog
- Fix ensures parity between legacy and scenes dashboard implementations

Fixes issue where Ctrl+O/Cmd+O was opening browser file dialog instead of
toggling shared crosshair modes in scenes-based dashboards.

* Remove waitForTimeout from e2e test

- Replace arbitrary timeouts with proper element waiting
- Use waitFor with visible state instead of setTimeout
- Improve test reliability and follow Playwright best practices

* Optimize e2e test for crosshair keyboard shortcut

- Remove unnecessary console logging and timeout settings
- Simplify assertions to only check the currently selected radio button
- Improve test performance by reducing DOM queries
- Focus on essential functionality verification

* Fix linting
2025-09-19 16:03:02 +00:00

148 lines
6.1 KiB
TypeScript

import { test, expect } from '@grafana/plugin-e2e';
test.describe(
'Keyboard shortcuts',
{
tag: ['@various'],
},
() => {
test.beforeEach(async ({ page, selectors }) => {
await page.goto('/');
// Wait for the page to load
const panelTitle = page.getByTestId(selectors.components.Panels.Panel.title('Latest from the blog'));
await expect(panelTitle).toBeVisible();
});
test('sequence shortcuts should work', async ({ page, selectors }) => {
// Navigate to explore with 'ge' shortcut
await page.keyboard.type('ge');
const exploreContainer = page.getByTestId(selectors.pages.Explore.General.container);
await expect(exploreContainer).toBeVisible();
// Navigate to profile with 'gp' shortcut
await page.keyboard.type('gp');
const preferencesSaveButton = page.getByTestId(selectors.components.UserProfile.preferencesSaveButton);
await expect(preferencesSaveButton).toBeVisible();
// Navigate back to home with 'gh' shortcut
await page.keyboard.type('gh');
const panelTitle = page.getByTestId(selectors.components.Panels.Panel.title('Latest from the blog'));
await expect(panelTitle).toBeVisible();
});
test('ctrl+z should zoom out the time range', async ({ page, selectors }) => {
// Navigate to explore
await page.keyboard.type('ge');
const exploreContainer = page.getByTestId(selectors.pages.Explore.General.container);
await expect(exploreContainer).toBeVisible();
// Set time range
const timePickerButton = page.getByTestId(selectors.components.TimePicker.openButton);
await timePickerButton.click();
const fromField = page.getByTestId(selectors.components.TimePicker.fromField);
await fromField.fill('2024-06-05 10:05:00');
const toField = page.getByTestId(selectors.components.TimePicker.toField);
await toField.fill('2024-06-05 10:06:00');
const applyTimeRangeButton = page.getByTestId(selectors.components.TimePicker.applyTimeRange);
await applyTimeRangeButton.click();
await page.keyboard.press('Control+z');
const expectedRange = 'Time range selected: 2024-06-05 10:03:30 to 2024-06-05 10:07:30';
await expect(timePickerButton).toHaveAttribute('aria-label', expectedRange);
});
test('time range shortcuts should work', async ({ page, selectors }) => {
// Navigate to explore
await page.keyboard.type('ge');
const exploreContainer = page.getByTestId(selectors.pages.Explore.General.container);
await expect(exploreContainer).toBeVisible();
// Set time range
const timePickerButton = page.getByTestId(selectors.components.TimePicker.openButton);
await timePickerButton.click();
const fromField = page.getByTestId(selectors.components.TimePicker.fromField);
await fromField.fill('2024-06-05 10:05:00');
const toField = page.getByTestId(selectors.components.TimePicker.toField);
await toField.fill('2024-06-05 10:06:00');
const applyTimeRangeButton = page.getByTestId(selectors.components.TimePicker.applyTimeRange);
await applyTimeRangeButton.click();
let expectedRange = 'Time range selected: 2024-06-05 10:05:00 to 2024-06-05 10:06:00';
await expect(timePickerButton).toHaveAttribute('aria-label', expectedRange);
// Use time range shortcut to move back
await page.keyboard.press('t');
await page.keyboard.press('ArrowLeft');
expectedRange = 'Time range selected: 2024-06-05 10:04:00 to 2024-06-05 10:05:00'; // 1 min back
await expect(timePickerButton).toHaveAttribute('aria-label', expectedRange);
});
test('ctrl+o should toggle shared crosshair', async ({ page, selectors }) => {
// Navigate to a new dashboard
await page.goto('/dashboard/new?orgId=1');
// Wait for dashboard to load
await page.waitForLoadState('networkidle');
// Wait for dashboard to be fully initialized by checking for dashboard content
await page
.locator('[data-testid*="dashboard"]')
.or(page.locator('text=Start your new dashboard'))
.first()
.waitFor({ state: 'visible' });
// Test the keyboard shortcut first in the main dashboard view
const currentUrl = page.url();
const modKey = process.platform === 'darwin' ? 'Meta' : 'Control';
// Test that mod+o works in the main dashboard (should not trigger file dialog)
console.log('Testing mod+o in main dashboard view...');
await page.keyboard.press(`${modKey}+o`);
expect(page.url()).toBe(currentUrl); // Should not navigate away
// Now open settings to check if the state actually changed
await page.keyboard.press('d');
await page.keyboard.press('s');
// Wait for settings page to load by checking for the General tab or settings content
await page
.locator('text=General')
.or(page.locator('[data-testid*="dashboard-settings"]'))
.waitFor({ state: 'visible' });
// Wait for Panel options section to be visible and scroll to it
const panelOptionsSection = page.locator('text=Panel options');
await panelOptionsSection.waitFor({ state: 'visible' });
await panelOptionsSection.scrollIntoViewIfNeeded();
// Wait for radio buttons to be visible
await page
.locator('[role="radiogroup"]')
.last()
.locator('input[type="radio"]')
.first()
.waitFor({ state: 'visible' });
// Check current state - after one mod+o press, it should be crosshair (1)
await expect(page.locator('[role="radiogroup"]').last().locator('input[type="radio"]').nth(1)).toBeChecked(); // Shared crosshair
// Test second press in the main dashboard view (should go to tooltip)
await page.keyboard.press(`${modKey}+o`);
await expect(page.locator('[role="radiogroup"]').last().locator('input[type="radio"]').nth(2)).toBeChecked(); // Shared tooltip
// Test third press in the main dashboard view (should go back to default)
await page.keyboard.press(`${modKey}+o`);
await expect(page.locator('[role="radiogroup"]').last().locator('input[type="radio"]').nth(0)).toBeChecked(); // Default
});
}
);