Playwright: Fix remaining various-suite tests (#110667)

* get trace-view-scrolling working in playwright

* almost fix frontend-sandbox-datasource test

* properly fix frontend-sandbox-datasource tests

* convert migrate-to-cloud to playwright

* prometheus-variable-editor tests in playwright

* enable prometheus-config tests in playwright

* run on frontend changes

* skip test

* remove various-suite
This commit is contained in:
Ashley Harrison 2025-09-08 16:57:39 +01:00 committed by GitHub
parent 454380431d
commit f4833ee2d8
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
15 changed files with 1066 additions and 8825 deletions

View File

@ -208,8 +208,6 @@ jobs:
fail-fast: false fail-fast: false
matrix: matrix:
include: include:
- suite: various-suite
path: e2e/various-suite
- suite: various-suite (old arch) - suite: various-suite (old arch)
path: e2e/old-arch/various-suite path: e2e/old-arch/various-suite
flags: --flags="--env dashboardScene=false" flags: --flags="--env dashboardScene=false"

File diff suppressed because it is too large Load Diff

View File

@ -1,28 +1,15 @@
import { random } from 'lodash'; import { random } from 'lodash';
import { test, expect, DataSourceConfigPage } from '@grafana/plugin-e2e'; import { test, expect } from '@grafana/plugin-e2e';
const DATASOURCE_ID = 'sandbox-test-datasource'; const DATASOURCE_ID = 'sandbox-test-datasource';
let DATASOURCE_CONNECTION_ID = '';
const DATASOURCE_TYPED_NAME = 'SandboxDatasourceInstance';
// Originally skipped due to flakiness/race conditions with same old arch test e2e/various-suite/frontend-sandbox-datasource.spec.ts test.describe(
// TODO: fix and remove skip
test.describe.skip(
'Datasource sandbox', 'Datasource sandbox',
{ {
tag: ['@various', '@wip'], tag: ['@various'],
}, },
() => { () => {
let configPage: DataSourceConfigPage;
test.beforeEach(async ({ createDataSourceConfigPage }) => {
// Add the datasource
configPage = await createDataSourceConfigPage({
type: 'sandbox',
name: DATASOURCE_TYPED_NAME,
});
});
test.describe('Config Editor', () => { test.describe('Config Editor', () => {
test.describe('Sandbox disabled', () => { test.describe('Sandbox disabled', () => {
test.beforeEach(async ({ page }) => { test.beforeEach(async ({ page }) => {
@ -31,9 +18,19 @@ test.describe.skip(
}); });
}); });
test('Should not render a sandbox wrapper around the datasource config editor', async ({ page }) => { test('Should not render a sandbox wrapper around the datasource config editor', async ({
page,
createDataSource,
}) => {
const TIMESTAMP = Date.now();
const DATASOURCE_TYPED_NAME = `SandboxDatasourceInstance-${TIMESTAMP}`;
// Add the datasource
const response = await createDataSource({
type: DATASOURCE_ID,
name: DATASOURCE_TYPED_NAME,
});
const DATASOURCE_CONNECTION_ID = response.uid;
await page.goto(`/connections/datasources/edit/${DATASOURCE_CONNECTION_ID}`); await page.goto(`/connections/datasources/edit/${DATASOURCE_CONNECTION_ID}`);
await page.waitForTimeout(300); // wait to prevent false positives because playwright checks too fast
const sandboxDiv = page.locator(`div[data-plugin-sandbox="${DATASOURCE_ID}"]`); const sandboxDiv = page.locator(`div[data-plugin-sandbox="${DATASOURCE_ID}"]`);
await expect(sandboxDiv).toBeHidden(); await expect(sandboxDiv).toBeHidden();
@ -47,12 +44,33 @@ test.describe.skip(
}); });
}); });
test('Should render a sandbox wrapper around the datasource config editor', async ({ page, selectors }) => { test('Should render a sandbox wrapper around the datasource config editor', async ({
page,
createDataSource,
}) => {
const TIMESTAMP = Date.now();
const DATASOURCE_TYPED_NAME = `SandboxDatasourceInstance-${TIMESTAMP}`;
// Add the datasource
const response = await createDataSource({
type: DATASOURCE_ID,
name: DATASOURCE_TYPED_NAME,
});
const DATASOURCE_CONNECTION_ID = response.uid;
await page.goto(`/connections/datasources/edit/${DATASOURCE_CONNECTION_ID}`);
const sandboxDiv = page.locator(`div[data-plugin-sandbox="${DATASOURCE_ID}"]`); const sandboxDiv = page.locator(`div[data-plugin-sandbox="${DATASOURCE_ID}"]`);
await expect(sandboxDiv).toBeVisible(); await expect(sandboxDiv).toBeVisible();
}); });
test('Should store values in jsonData and secureJsonData correctly', async ({ page }) => { test('Should store values in jsonData and secureJsonData correctly', async ({ page, createDataSource }) => {
const TIMESTAMP = Date.now();
const DATASOURCE_TYPED_NAME = `SandboxDatasourceInstance-${TIMESTAMP}`;
// Add the datasource
const response = await createDataSource({
type: DATASOURCE_ID,
name: DATASOURCE_TYPED_NAME,
});
const DATASOURCE_CONNECTION_ID = response.uid;
await page.goto(`/connections/datasources/edit/${DATASOURCE_CONNECTION_ID}`); await page.goto(`/connections/datasources/edit/${DATASOURCE_CONNECTION_ID}`);
const valueToStore = 'test' + random(100); const valueToStore = 'test' + random(100);
@ -62,10 +80,10 @@ test.describe.skip(
await queryInput.fill(valueToStore); await queryInput.fill(valueToStore);
await expect(queryInput).toHaveValue(valueToStore); await expect(queryInput).toHaveValue(valueToStore);
const saveButton = page.getByTestId('data-testid Data source settings page Save and test button'); const saveButton = page.getByTestId('data-testid Data source settings page Save and Test button');
await saveButton.click(); await saveButton.click();
const alert = page.locator('[data-testid="data-testid Alert"]'); const alert = page.getByTestId('data-testid Data source settings page Alert');
await expect(alert).toBeVisible(); await expect(alert).toBeVisible();
await expect(alert).toContainText('Sandbox Success'); await expect(alert).toContainText('Sandbox Success');
@ -85,7 +103,20 @@ test.describe.skip(
}); });
}); });
test('Should not wrap the query editor in a sandbox wrapper', async ({ page, dashboardPage, selectors }) => { test('Should not wrap the query editor in a sandbox wrapper', async ({
page,
createDataSource,
dashboardPage,
selectors,
}) => {
const TIMESTAMP = Date.now();
const DATASOURCE_TYPED_NAME = `SandboxDatasourceInstance-${TIMESTAMP}`;
// Add the datasource
const response = await createDataSource({
type: DATASOURCE_ID,
name: DATASOURCE_TYPED_NAME,
});
const DATASOURCE_CONNECTION_ID = response.uid;
await page.goto('/explore'); await page.goto('/explore');
const dataSourcePicker = dashboardPage.getByGrafanaSelector(selectors.components.DataSourcePicker.container); const dataSourcePicker = dashboardPage.getByGrafanaSelector(selectors.components.DataSourcePicker.container);
@ -103,12 +134,19 @@ test.describe.skip(
); );
await expect(breadcrumb).toBeVisible(); await expect(breadcrumb).toBeVisible();
await page.waitForTimeout(300); // wait to prevent false positives because playwright checks too fast
const sandboxDiv = page.locator(`div[data-plugin-sandbox="${DATASOURCE_ID}"]`); const sandboxDiv = page.locator(`div[data-plugin-sandbox="${DATASOURCE_ID}"]`);
await expect(sandboxDiv).toBeHidden(); await expect(sandboxDiv).toBeHidden();
}); });
test('Should accept values when typed', async ({ page, dashboardPage, selectors }) => { test('Should accept values when typed', async ({ page, createDataSource, dashboardPage, selectors }) => {
const TIMESTAMP = Date.now();
const DATASOURCE_TYPED_NAME = `SandboxDatasourceInstance-${TIMESTAMP}`;
// Add the datasource
const response = await createDataSource({
type: DATASOURCE_ID,
name: DATASOURCE_TYPED_NAME,
});
const DATASOURCE_CONNECTION_ID = response.uid;
await page.goto('/explore'); await page.goto('/explore');
const dataSourcePicker = dashboardPage.getByGrafanaSelector(selectors.components.DataSourcePicker.container); const dataSourcePicker = dashboardPage.getByGrafanaSelector(selectors.components.DataSourcePicker.container);
@ -142,7 +180,20 @@ test.describe.skip(
}); });
}); });
test('Should wrap the query editor in a sandbox wrapper', async ({ page, dashboardPage, selectors }) => { test('Should wrap the query editor in a sandbox wrapper', async ({
page,
createDataSource,
dashboardPage,
selectors,
}) => {
const TIMESTAMP = Date.now();
const DATASOURCE_TYPED_NAME = `SandboxDatasourceInstance-${TIMESTAMP}`;
// Add the datasource
const response = await createDataSource({
type: DATASOURCE_ID,
name: DATASOURCE_TYPED_NAME,
});
const DATASOURCE_CONNECTION_ID = response.uid;
await page.goto('/explore'); await page.goto('/explore');
const dataSourcePicker = dashboardPage.getByGrafanaSelector(selectors.components.DataSourcePicker.container); const dataSourcePicker = dashboardPage.getByGrafanaSelector(selectors.components.DataSourcePicker.container);
@ -164,7 +215,15 @@ test.describe.skip(
await expect(sandboxDiv).toBeVisible(); await expect(sandboxDiv).toBeVisible();
}); });
test('Should accept values when typed', async ({ page, dashboardPage, selectors }) => { test('Should accept values when typed', async ({ page, createDataSource, dashboardPage, selectors }) => {
const TIMESTAMP = Date.now();
const DATASOURCE_TYPED_NAME = `SandboxDatasourceInstance-${TIMESTAMP}`;
// Add the datasource
const response = await createDataSource({
type: DATASOURCE_ID,
name: DATASOURCE_TYPED_NAME,
});
const DATASOURCE_CONNECTION_ID = response.uid;
await page.goto('/explore'); await page.goto('/explore');
const dataSourcePicker = dashboardPage.getByGrafanaSelector(selectors.components.DataSourcePicker.container); const dataSourcePicker = dashboardPage.getByGrafanaSelector(selectors.components.DataSourcePicker.container);

View File

@ -3,7 +3,7 @@ import { test, expect } from '@grafana/plugin-e2e';
test.describe( test.describe(
'Migrate to Cloud (On-prem)', 'Migrate to Cloud (On-prem)',
{ {
tag: ['@various', '@wip'], tag: ['@various'],
}, },
() => { () => {
test.describe('with mocked calls to the API backend', () => { test.describe('with mocked calls to the API backend', () => {
@ -59,8 +59,7 @@ test.describe(
total: SNAPSHOT_RESULTS.length, total: SNAPSHOT_RESULTS.length,
}; };
// TODO: fix the test. It makes it most of the way through. Probably a network mock issue. test('creates and uploads a snapshot successfully', async ({ page }) => {
test.skip('creates and uploads a snapshot successfully', async ({ page }) => {
// Visit the migrate to cloud onprem page // Visit the migrate to cloud onprem page
await page.goto('/admin/migrate-to-cloud'); await page.goto('/admin/migrate-to-cloud');
@ -75,7 +74,7 @@ test.describe(
await tokenInput.fill('test'); await tokenInput.fill('test');
// Mock API responses // Mock API responses
await page.route(/api\/cloudmigration\/migration/, async (route) => { await page.route(/api\/cloudmigration\/migration$/, async (route) => {
if (route.request().method() === 'POST') { if (route.request().method() === 'POST') {
await route.fulfill({ await route.fulfill({
status: 200, status: 200,
@ -93,7 +92,7 @@ test.describe(
} }
}); });
await page.route(/api\/cloudmigration\/migration\/${SESSION_UID}\/snapshots\?page=1&limit=1/, async (route) => { await page.route(`api/cloudmigration/migration/${SESSION_UID}/snapshots?page=1&limit=1*`, async (route) => {
await route.fulfill({ await route.fulfill({
status: 200, status: 200,
contentType: 'application/json', contentType: 'application/json',
@ -141,14 +140,14 @@ test.describe(
'migrate-to-cloud-configure-snapshot-checkbox-resource-mute_timing' 'migrate-to-cloud-configure-snapshot-checkbox-resource-mute_timing'
); );
await muteTimingCheckbox.uncheck({ force: true }); await muteTimingCheckbox.uncheck({ force: true });
await expect(muteTimingCheckbox).not.toBeChecked(); await expect(muteTimingCheckbox).toBeChecked({ checked: false });
// Validate that those resources are now unchecked // Validate that those resources are now unchecked
for (const resourceType of ['alert_rule', 'alert_rule_group', 'include-all']) { for (const resourceType of ['alert_rule', 'alert_rule_group', 'include-all']) {
const checkbox = page.getByTestId( const checkbox = page.getByTestId(
`migrate-to-cloud-configure-snapshot-checkbox-resource-${resourceType.toLowerCase()}` `migrate-to-cloud-configure-snapshot-checkbox-resource-${resourceType.toLowerCase()}`
); );
await expect(checkbox).not.toBeChecked(); await expect(checkbox).toBeChecked({ checked: false });
} }
// Check everything again because we can // Check everything again because we can
@ -164,7 +163,7 @@ test.describe(
} }
// Mock snapshot creation // Mock snapshot creation
await page.route(/api\/cloudmigration\/migration\/${SESSION_UID}\/snapshot/, async (route) => { await page.route(new RegExp(`api\/cloudmigration\/migration\/${SESSION_UID}\/snapshot$`), async (route) => {
await route.fulfill({ await route.fulfill({
status: 200, status: 200,
contentType: 'application/json', contentType: 'application/json',
@ -174,7 +173,7 @@ test.describe(
}); });
}); });
await page.route(/api\/cloudmigration\/migration\/${SESSION_UID}\/snapshots\?page=1&limit=1/, async (route) => { await page.route(`api/cloudmigration/migration/${SESSION_UID}/snapshots?page=1&limit=1*`, async (route) => {
await route.fulfill({ await route.fulfill({
status: 200, status: 200,
contentType: 'application/json', contentType: 'application/json',
@ -194,7 +193,7 @@ test.describe(
let getSnapshotCalled = false; let getSnapshotCalled = false;
await page.route( await page.route(
/api\/cloudmigration\/migration\/${SESSION_UID}\/snapshot\/${SNAPSHOT_UID1}\?resultPage=1&resultLimit=50/, `api/cloudmigration/migration/${SESSION_UID}/snapshot/${SNAPSHOT_UID1}?resultPage=1&resultLimit=50*`,
async (route) => { async (route) => {
if (!getSnapshotCalled) { if (!getSnapshotCalled) {
getSnapshotCalled = true; getSnapshotCalled = true;
@ -238,9 +237,12 @@ test.describe(
await expect(buildSnapshotButton).toBeVisible(); await expect(buildSnapshotButton).toBeVisible();
await buildSnapshotButton.click(); await buildSnapshotButton.click();
const uploadButton = page.getByTestId('migrate-to-cloud-summary-upload-snapshot-button');
await expect(uploadButton).toBeVisible();
// Mock upload // Mock upload
const uploadSnapshot = await page.route( await page.route(
/api\/cloudmigration\/migration\/${SESSION_UID}\/snapshot\/${SNAPSHOT_UID1}\/upload/, `api/cloudmigration/migration/${SESSION_UID}/snapshot/${SNAPSHOT_UID1}/upload`,
async (route) => { async (route) => {
await route.fulfill({ await route.fulfill({
status: 200, status: 200,
@ -250,17 +252,8 @@ test.describe(
} }
); );
// Upload the snapshot
const uploadButton = page.getByTestId('migrate-to-cloud-summary-upload-snapshot-button');
await expect(uploadButton).toBeVisible();
await uploadButton.focus();
await uploadButton.click({ force: true });
// Mock uploading status // Mock uploading status
const getSnapshotListUploading = await page.route( await page.route(`api/cloudmigration/migration/${SESSION_UID}/snapshots?page=1&limit=1*`, async (route) => {
/api\/cloudmigration\/migration\/${SESSION_UID}\/snapshots\?page=1&limit=1/,
async (route) => {
await route.fulfill({ await route.fulfill({
status: 200, status: 200,
contentType: 'application/json', contentType: 'application/json',
@ -276,13 +269,12 @@ test.describe(
], ],
}), }),
}); });
} });
);
// Simulate the snapshot being uploaded // Simulate the snapshot being uploaded
let getSnapshotUploadingCalls = 0; let getSnapshotUploadingCalls = 0;
const getSnapshotUploading = await page.route( await page.route(
/api\/cloudmigration\/migration\/${SESSION_UID}\/snapshot\/${SNAPSHOT_UID1}\?resultPage=1&resultLimit=50/, `api/cloudmigration/migration/${SESSION_UID}/snapshot/${SNAPSHOT_UID1}?resultPage=1&resultLimit=50*`,
async (route) => { async (route) => {
if (getSnapshotUploadingCalls <= 1) { if (getSnapshotUploadingCalls <= 1) {
await route.fulfill({ await route.fulfill({
@ -312,14 +304,11 @@ test.describe(
results: SNAPSHOT_RESULTS.map((r) => ({ ...r, status: 'OK' })), results: SNAPSHOT_RESULTS.map((r) => ({ ...r, status: 'OK' })),
stats: { stats: {
types: STATS.types, types: STATS.types,
statuses: SNAPSHOT_RESULTS.reduce( statuses: SNAPSHOT_RESULTS.reduce<Record<string, number>>((acc, r) => {
(acc, r) => {
const status = (r as { status?: string }).status || 'UNKNOWN'; const status = (r as { status?: string }).status || 'UNKNOWN';
acc[status] = (acc[status] || 0) + 1; acc[status] = (acc[status] || 0) + 1;
return acc; return acc;
}, }, {}),
{} as Record<string, number>
),
total: SNAPSHOT_RESULTS.length, total: SNAPSHOT_RESULTS.length,
}, },
}), }),
@ -328,12 +317,12 @@ test.describe(
} }
); );
await uploadSnapshot; // Upload the snapshot
await getSnapshotListUploading; await uploadButton.focus();
await getSnapshotUploading; await uploadButton.click({ force: true });
// At least some of the items are marked with "Uploaded to cloud" status // At least some of the items are marked with "Uploaded to cloud" status
await expect(page.getByText('Uploaded to cloud')).toBeVisible(); await expect(page.getByText('Uploaded to cloud')).toHaveCount(22);
// We can now reconfigure the snapshot // We can now reconfigure the snapshot
const reconfigureButton = page.getByTestId('migrate-to-cloud-summary-reconfigure-snapshot-button'); const reconfigureButton = page.getByTestId('migrate-to-cloud-summary-reconfigure-snapshot-button');

View File

@ -1,30 +1,25 @@
import { Page } from 'playwright-core';
import { test, expect } from '@grafana/plugin-e2e'; import { test, expect } from '@grafana/plugin-e2e';
// Todo: Fix datasource creation test.describe(
test.describe.skip(
'Prometheus config', 'Prometheus config',
{ {
tag: ['@various'], tag: ['@various'],
}, },
() => { () => {
const DATASOURCE_ID = 'Prometheus'; const DATASOURCE_PREFIX = 'PrometheusConfig';
const DATASOURCE_TYPED_NAME = 'PrometheusDatasourceInstance';
test.beforeEach(async ({ page, selectors, createDataSourceConfigPage }) => {
// Navigate to add data source page
await page.goto('/datasources/new');
// Select the Prometheus data source
const prometheusPlugin = page.getByRole('button', { name: DATASOURCE_ID });
await prometheusPlugin.scrollIntoViewIfNeeded();
await expect(prometheusPlugin).toBeVisible();
await prometheusPlugin.click();
});
test('should have the following components: connection settings, managed alerts, scrape interval, query timeout, default editor, disable metric lookup, prometheus type, cache level, incremental querying, disable recording rules, custom query parameters, http method', async ({ test('should have the following components: connection settings, managed alerts, scrape interval, query timeout, default editor, disable metric lookup, prometheus type, cache level, incremental querying, disable recording rules, custom query parameters, http method', async ({
page, page,
selectors, selectors,
createDataSourceConfigPage,
}) => { }) => {
const DATASOURCE_NAME = `${DATASOURCE_PREFIX}_${Date.now()}`;
const configPage = await createDataSourceConfigPage({
type: 'prometheus',
name: DATASOURCE_NAME,
});
// connection settings // connection settings
const connectionSettings = page.getByLabel( const connectionSettings = page.getByLabel(
selectors.components.DataSource.Prometheus.configPage.connectionSettings selectors.components.DataSource.Prometheus.configPage.connectionSettings
@ -33,72 +28,85 @@ test.describe.skip(
// managed alerts // managed alerts
const manageAlerts = page.locator(`#${selectors.components.DataSource.Prometheus.configPage.manageAlerts}`); const manageAlerts = page.locator(`#${selectors.components.DataSource.Prometheus.configPage.manageAlerts}`);
await manageAlerts.scrollIntoViewIfNeeded();
await expect(manageAlerts).toBeVisible(); await expect(manageAlerts).toBeVisible();
// scrape interval // scrape interval
const scrapeInterval = page.getByTestId(selectors.components.DataSource.Prometheus.configPage.scrapeInterval); const scrapeInterval = configPage.getByGrafanaSelector(
await scrapeInterval.scrollIntoViewIfNeeded(); selectors.components.DataSource.Prometheus.configPage.scrapeInterval
);
await expect(scrapeInterval).toBeVisible(); await expect(scrapeInterval).toBeVisible();
// query timeout // query timeout
const queryTimeout = page.getByTestId(selectors.components.DataSource.Prometheus.configPage.queryTimeout); const queryTimeout = configPage.getByGrafanaSelector(
await queryTimeout.scrollIntoViewIfNeeded(); selectors.components.DataSource.Prometheus.configPage.queryTimeout
);
await expect(queryTimeout).toBeVisible(); await expect(queryTimeout).toBeVisible();
// default editor // default editor
const defaultEditor = page.getByTestId(selectors.components.DataSource.Prometheus.configPage.defaultEditor); const defaultEditor = configPage.getByGrafanaSelector(
await defaultEditor.scrollIntoViewIfNeeded(); selectors.components.DataSource.Prometheus.configPage.defaultEditor
);
await expect(defaultEditor).toBeVisible(); await expect(defaultEditor).toBeVisible();
// disable metric lookup // disable metric lookup
const disableMetricLookup = page.locator( const disableMetricLookup = page.locator(
`#${selectors.components.DataSource.Prometheus.configPage.disableMetricLookup}` `#${selectors.components.DataSource.Prometheus.configPage.disableMetricLookup}`
); );
await disableMetricLookup.scrollIntoViewIfNeeded();
await expect(disableMetricLookup).toBeVisible(); await expect(disableMetricLookup).toBeVisible();
// prometheus type // prometheus type
const prometheusType = page.getByTestId(selectors.components.DataSource.Prometheus.configPage.prometheusType); const prometheusType = configPage.getByGrafanaSelector(
await prometheusType.scrollIntoViewIfNeeded(); selectors.components.DataSource.Prometheus.configPage.prometheusType
);
await expect(prometheusType).toBeVisible(); await expect(prometheusType).toBeVisible();
// cache level // cache level
const cacheLevel = page.getByTestId(selectors.components.DataSource.Prometheus.configPage.cacheLevel); const cacheLevel = configPage.getByGrafanaSelector(
await cacheLevel.scrollIntoViewIfNeeded(); selectors.components.DataSource.Prometheus.configPage.cacheLevel
);
await expect(cacheLevel).toBeVisible(); await expect(cacheLevel).toBeVisible();
// incremental querying // incremental querying
const incrementalQuerying = page.locator( const incrementalQuerying = page.locator(
`#${selectors.components.DataSource.Prometheus.configPage.incrementalQuerying}` `#${selectors.components.DataSource.Prometheus.configPage.incrementalQuerying}`
); );
await incrementalQuerying.scrollIntoViewIfNeeded();
await expect(incrementalQuerying).toBeVisible(); await expect(incrementalQuerying).toBeVisible();
// disable recording rules // disable recording rules
const disableRecordingRules = page.locator( const disableRecordingRules = page.locator(
`#${selectors.components.DataSource.Prometheus.configPage.disableRecordingRules}` `#${selectors.components.DataSource.Prometheus.configPage.disableRecordingRules}`
); );
await disableRecordingRules.scrollIntoViewIfNeeded();
await expect(disableRecordingRules).toBeVisible(); await expect(disableRecordingRules).toBeVisible();
// custom query parameters // custom query parameters
const customQueryParameters = page.getByTestId( const customQueryParameters = configPage.getByGrafanaSelector(
selectors.components.DataSource.Prometheus.configPage.customQueryParameters selectors.components.DataSource.Prometheus.configPage.customQueryParameters
); );
await customQueryParameters.scrollIntoViewIfNeeded();
await expect(customQueryParameters).toBeVisible(); await expect(customQueryParameters).toBeVisible();
// http method // http method
const httpMethod = page.getByTestId(selectors.components.DataSource.Prometheus.configPage.httpMethod); const httpMethod = configPage.getByGrafanaSelector(
await httpMethod.scrollIntoViewIfNeeded(); selectors.components.DataSource.Prometheus.configPage.httpMethod
);
await expect(httpMethod).toBeVisible(); await expect(httpMethod).toBeVisible();
}); });
test('should save the default editor when navigating to explore', async ({ page, selectors }) => { test('should save the default editor when navigating to explore', async ({
createDataSourceConfigPage,
explorePage,
page,
selectors,
}) => {
const DATASOURCE_NAME = `${DATASOURCE_PREFIX}_${Date.now()}`;
const configPage = await createDataSourceConfigPage({
type: 'prometheus',
name: DATASOURCE_NAME,
});
// Click on default editor // Click on default editor
const defaultEditor = page.getByTestId(selectors.components.DataSource.Prometheus.configPage.defaultEditor); const defaultEditor = configPage.getByGrafanaSelector(
await defaultEditor.scrollIntoViewIfNeeded(); selectors.components.DataSource.Prometheus.configPage.defaultEditor
);
await expect(defaultEditor).toBeVisible(); await expect(defaultEditor).toBeVisible();
await defaultEditor.click(); await defaultEditor.click();
@ -111,17 +119,12 @@ test.describe.skip(
); );
await connectionSettings.fill('http://prom-url:9090'); await connectionSettings.fill('http://prom-url:9090');
// Set data source name
const nameInput = page.getByTestId(selectors.pages.DataSource.name);
await nameInput.clear();
await nameInput.fill(DATASOURCE_TYPED_NAME);
// Save and test // Save and test
const saveAndTestButton = page.getByTestId(selectors.pages.DataSource.saveAndTest); const saveAndTestButton = configPage.getByGrafanaSelector(selectors.pages.DataSource.saveAndTest);
await saveAndTestButton.click(); await saveAndTestButton.click();
// Navigate to explore // Navigate to explore
await page.goto('/explore'); await explorePage.goto();
// Select the data source // Select the data source
const dataSourcePicker = page.getByTestId(selectors.components.DataSourcePicker.container); const dataSourcePicker = page.getByTestId(selectors.components.DataSourcePicker.container);
@ -129,7 +132,7 @@ test.describe.skip(
await dataSourcePicker.click(); await dataSourcePicker.click();
// Type the data source name and press enter // Type the data source name and press enter
await page.keyboard.type(DATASOURCE_TYPED_NAME); await page.keyboard.type(DATASOURCE_NAME);
await page.keyboard.press('Enter'); await page.keyboard.press('Enter');
// Verify the builder metric select is visible // Verify the builder metric select is visible
@ -139,10 +142,21 @@ test.describe.skip(
await expect(metricSelect).toBeVisible(); await expect(metricSelect).toBeVisible();
}); });
test('should allow a user to add the version when the Prom type is selected', async ({ page, selectors }) => { test('should allow a user to add the version when the Prom type is selected', async ({
createDataSourceConfigPage,
page,
selectors,
}) => {
const DATASOURCE_NAME = `${DATASOURCE_PREFIX}_${Date.now()}`;
const configPage = await createDataSourceConfigPage({
type: 'prometheus',
name: DATASOURCE_NAME,
});
// Click on prometheus type // Click on prometheus type
const prometheusType = page.getByTestId(selectors.components.DataSource.Prometheus.configPage.prometheusType); const prometheusType = configPage.getByGrafanaSelector(
await prometheusType.scrollIntoViewIfNeeded(); selectors.components.DataSource.Prometheus.configPage.prometheusType
);
await expect(prometheusType).toBeVisible(); await expect(prometheusType).toBeVisible();
await prometheusType.click(); await prometheusType.click();
@ -150,43 +164,54 @@ test.describe.skip(
await selectOption(page, 'Prometheus'); await selectOption(page, 'Prometheus');
// Verify prometheus version is visible // Verify prometheus version is visible
const prometheusVersion = page.getByTestId( const prometheusVersion = configPage.getByGrafanaSelector(
selectors.components.DataSource.Prometheus.configPage.prometheusVersion selectors.components.DataSource.Prometheus.configPage.prometheusVersion
); );
await prometheusVersion.scrollIntoViewIfNeeded();
await expect(prometheusVersion).toBeVisible(); await expect(prometheusVersion).toBeVisible();
}); });
test('should have a cache level component', async ({ page, selectors }) => { test('should have a cache level component', async ({ createDataSourceConfigPage, page, selectors }) => {
const DATASOURCE_NAME = `${DATASOURCE_PREFIX}_${Date.now()}`;
const configPage = await createDataSourceConfigPage({
type: 'prometheus',
name: DATASOURCE_NAME,
});
// Verify cache level is visible // Verify cache level is visible
const cacheLevel = page.getByTestId(selectors.components.DataSource.Prometheus.configPage.cacheLevel); const cacheLevel = configPage.getByGrafanaSelector(
await cacheLevel.scrollIntoViewIfNeeded(); selectors.components.DataSource.Prometheus.configPage.cacheLevel
);
await expect(cacheLevel).toBeVisible(); await expect(cacheLevel).toBeVisible();
}); });
test('should allow a user to select a query overlap window when incremental querying is selected', async ({ test('should allow a user to select a query overlap window when incremental querying is selected', async ({
createDataSourceConfigPage,
page, page,
selectors, selectors,
}) => { }) => {
const DATASOURCE_NAME = `${DATASOURCE_PREFIX}_${Date.now()}`;
const configPage = await createDataSourceConfigPage({
type: 'prometheus',
name: DATASOURCE_NAME,
});
// Check the incremental querying checkbox // Check the incremental querying checkbox
const incrementalQuerying = page.locator( const incrementalQuerying = page.locator(
`#${selectors.components.DataSource.Prometheus.configPage.incrementalQuerying}` `#${selectors.components.DataSource.Prometheus.configPage.incrementalQuerying}`
); );
await incrementalQuerying.scrollIntoViewIfNeeded();
await expect(incrementalQuerying).toBeVisible(); await expect(incrementalQuerying).toBeVisible();
await incrementalQuerying.check({ force: true }); await incrementalQuerying.check({ force: true });
// Verify query overlap window is visible // Verify query overlap window is visible
const queryOverlapWindow = page.getByTestId( const queryOverlapWindow = configPage.getByGrafanaSelector(
selectors.components.DataSource.Prometheus.configPage.queryOverlapWindow selectors.components.DataSource.Prometheus.configPage.queryOverlapWindow
); );
await queryOverlapWindow.scrollIntoViewIfNeeded();
await expect(queryOverlapWindow).toBeVisible(); await expect(queryOverlapWindow).toBeVisible();
}); });
} }
); );
async function selectOption(page, option) { async function selectOption(page: Page, option: string) {
const optionElement = page.getByRole('option').filter({ hasText: option }); const optionElement = page.getByRole('option').filter({ hasText: option });
await expect(optionElement).toBeVisible(); await expect(optionElement).toBeVisible();
await optionElement.click(); await optionElement.click();

View File

@ -1,14 +1,20 @@
import { Page } from 'playwright-core'; import { Page } from 'playwright-core';
import { test, expect, E2ESelectorGroups } from '@grafana/plugin-e2e'; import {
test,
expect,
E2ESelectorGroups,
CreateDataSourceArgs,
DataSourceSettings,
DataSourceConfigPage,
} from '@grafana/plugin-e2e';
import { getResources } from '../utils/prometheus-helpers'; import { getResources } from '../utils/prometheus-helpers';
// TODO: fix some tests. Race conditions with other tests in the file cause some to fail. test.describe(
test.describe.skip(
'Prometheus query editor', 'Prometheus query editor',
{ {
tag: ['@various', '@wip'], tag: ['@various'],
}, },
() => { () => {
const DATASOURCE_ID = 'Prometheus'; const DATASOURCE_ID = 'Prometheus';
@ -18,18 +24,25 @@ test.describe.skip(
/** /**
* Create and save a Prometheus data source, navigate to code or builder * Create and save a Prometheus data source, navigate to code or builder
*/ */
async function navigateToEditor(page: Page, selectors: E2ESelectorGroups, editorType: string, name: string) { async function navigateToEditor(
// Navigate to add data source page page: Page,
await page.goto('/datasources/new'); selectors: E2ESelectorGroups,
createDataSource: (args: CreateDataSourceArgs) => Promise<DataSourceSettings>,
editorType: EditorType,
name: string,
gotoDataSourceConfigPage: (uid: string) => Promise<DataSourceConfigPage>
) {
const { uid } = await createDataSource({
type: 'prometheus',
name,
});
// Select the Prometheus data source const configPage = await gotoDataSourceConfigPage(uid);
const prometheusPlugin = page.getByRole('button', { name: DATASOURCE_ID });
await prometheusPlugin.scrollIntoViewIfNeeded();
await expect(prometheusPlugin).toBeVisible();
await prometheusPlugin.click();
// Choose default editor // Choose default editor
const defaultEditor = page.getByTestId(selectors.components.DataSource.Prometheus.configPage.defaultEditor); const defaultEditor = configPage.getByGrafanaSelector(
selectors.components.DataSource.Prometheus.configPage.defaultEditor
);
await defaultEditor.scrollIntoViewIfNeeded(); await defaultEditor.scrollIntoViewIfNeeded();
await expect(defaultEditor).toBeVisible(); await expect(defaultEditor).toBeVisible();
await defaultEditor.click(); await defaultEditor.click();
@ -42,13 +55,10 @@ test.describe.skip(
); );
await connectionSettings.fill('http://prom-url:9090'); await connectionSettings.fill('http://prom-url:9090');
// Name the DS const saveResponse = page.waitForResponse((resp) => resp.url().includes('/api/datasources'));
const nameInput = page.getByTestId(selectors.pages.DataSource.name); const saveAndTestButton = configPage.getByGrafanaSelector(selectors.pages.DataSource.saveAndTest);
await nameInput.clear();
await nameInput.fill(name);
const saveAndTestButton = page.getByTestId(selectors.pages.DataSource.saveAndTest);
await saveAndTestButton.click(); await saveAndTestButton.click();
await saveResponse;
// Visit explore // Visit explore
await page.goto('/explore'); await page.goto('/explore');
@ -64,24 +74,42 @@ test.describe.skip(
await dataSourceOption.click(); await dataSourceOption.click();
} }
test('should have a kickstart component', async ({ page, selectors }) => { test('should have a kickstart component', async ({
await navigateToEditor(page, selectors, 'Code', 'prometheus'); createDataSource,
page,
selectors,
gotoDataSourceConfigPage,
}) => {
const DATASOURCE_NAME = `prometheus_${Date.now()}`;
await navigateToEditor(page, selectors, createDataSource, 'Code', DATASOURCE_NAME, gotoDataSourceConfigPage);
const queryPatterns = page.getByTestId(selectors.components.QueryBuilder.queryPatterns); const queryPatterns = page.getByTestId(selectors.components.QueryBuilder.queryPatterns);
await queryPatterns.scrollIntoViewIfNeeded(); await queryPatterns.scrollIntoViewIfNeeded();
await expect(queryPatterns).toBeVisible(); await expect(queryPatterns).toBeVisible();
}); });
test('should have an explain component', async ({ page, selectors }) => { test('should have an explain component', async ({
await navigateToEditor(page, selectors, 'Code', 'prometheus'); createDataSource,
gotoDataSourceConfigPage,
page,
selectors,
}) => {
const DATASOURCE_NAME = `prometheus_${Date.now()}`;
await navigateToEditor(page, selectors, createDataSource, 'Code', DATASOURCE_NAME, gotoDataSourceConfigPage);
const explain = page.getByTestId(selectors.components.DataSource.Prometheus.queryEditor.explain); const explain = page.getByTestId(selectors.components.DataSource.Prometheus.queryEditor.explain);
await explain.scrollIntoViewIfNeeded(); await explain.scrollIntoViewIfNeeded();
await expect(explain).toBeVisible(); await expect(explain).toBeVisible();
}); });
test('should have an editor toggle component', async ({ page, selectors }) => { test('should have an editor toggle component', async ({
await navigateToEditor(page, selectors, 'Code', 'prometheus'); createDataSource,
gotoDataSourceConfigPage,
page,
selectors,
}) => {
const DATASOURCE_NAME = `prometheus_${Date.now()}`;
await navigateToEditor(page, selectors, createDataSource, 'Code', DATASOURCE_NAME, gotoDataSourceConfigPage);
const editorToggle = page.getByTestId(selectors.components.DataSource.Prometheus.queryEditor.editorToggle); const editorToggle = page.getByTestId(selectors.components.DataSource.Prometheus.queryEditor.editorToggle);
await editorToggle.scrollIntoViewIfNeeded(); await editorToggle.scrollIntoViewIfNeeded();
@ -89,10 +117,13 @@ test.describe.skip(
}); });
test('should have an options component with legend, format, step, type and exemplars', async ({ test('should have an options component with legend, format, step, type and exemplars', async ({
createDataSource,
gotoDataSourceConfigPage,
page, page,
selectors, selectors,
}) => { }) => {
await navigateToEditor(page, selectors, 'Code', 'prometheus'); const DATASOURCE_NAME = `prometheus_${Date.now()}`;
await navigateToEditor(page, selectors, createDataSource, 'Code', DATASOURCE_NAME, gotoDataSourceConfigPage);
// Open options // Open options
const options = page.getByTestId(selectors.components.DataSource.Prometheus.queryEditor.options); const options = page.getByTestId(selectors.components.DataSource.Prometheus.queryEditor.options);
@ -102,36 +133,40 @@ test.describe.skip(
// Check options // Check options
const legend = page.getByTestId(selectors.components.DataSource.Prometheus.queryEditor.legend); const legend = page.getByTestId(selectors.components.DataSource.Prometheus.queryEditor.legend);
await legend.scrollIntoViewIfNeeded();
await expect(legend).toBeVisible(); await expect(legend).toBeVisible();
const format = page.getByTestId(selectors.components.DataSource.Prometheus.queryEditor.format); const format = page.getByTestId(selectors.components.DataSource.Prometheus.queryEditor.format);
await format.scrollIntoViewIfNeeded();
await expect(format).toBeVisible(); await expect(format).toBeVisible();
const step = page.locator('[data-test-id="prometheus-step"]'); const step = page.getByTestId(selectors.components.DataSource.Prometheus.queryEditor.step);
await step.scrollIntoViewIfNeeded();
await expect(step).toBeVisible(); await expect(step).toBeVisible();
const type = page.getByTestId(selectors.components.DataSource.Prometheus.queryEditor.type); const type = page.getByTestId(selectors.components.DataSource.Prometheus.queryEditor.type);
await type.scrollIntoViewIfNeeded();
await expect(type).toBeVisible(); await expect(type).toBeVisible();
const exemplars = page.getByTestId('prometheus-exemplars'); const exemplars = page.getByTestId(selectors.components.DataSource.Prometheus.queryEditor.exemplars);
await exemplars.scrollIntoViewIfNeeded();
await expect(exemplars).toBeVisible(); await expect(exemplars).toBeVisible();
}); });
test.describe('Code editor', () => { test.describe('Code editor', () => {
test('navigates to the code editor with editor type as code', async ({ page, selectors }) => { test('navigates to the code editor with editor type as code', async ({
await navigateToEditor(page, selectors, 'Code', 'prometheusCode'); createDataSource,
}); gotoDataSourceConfigPage,
test('navigates to the code editor and opens the metrics browser with metric search, labels, label values, and all components', async ({
page, page,
selectors, selectors,
}) => { }) => {
await navigateToEditor(page, selectors, 'Code', 'prometheusCode'); const DATASOURCE_NAME = `prometheusCode_${Date.now()}`;
await navigateToEditor(page, selectors, createDataSource, 'Code', DATASOURCE_NAME, gotoDataSourceConfigPage);
});
test('navigates to the code editor and opens the metrics browser with metric search, labels, label values, and all components', async ({
createDataSource,
gotoDataSourceConfigPage,
page,
selectors,
}) => {
const DATASOURCE_NAME = `prometheusCode_${Date.now()}`;
await navigateToEditor(page, selectors, createDataSource, 'Code', DATASOURCE_NAME, gotoDataSourceConfigPage);
await getResources(page); await getResources(page);
@ -179,8 +214,14 @@ test.describe.skip(
await expect(clear).toBeVisible(); await expect(clear).toBeVisible();
}); });
test('selects a metric in the metrics browser and uses the query', async ({ page, selectors }) => { test('selects a metric in the metrics browser and uses the query', async ({
await navigateToEditor(page, selectors, 'Code', 'prometheusCode'); createDataSource,
gotoDataSourceConfigPage,
page,
selectors,
}) => {
const DATASOURCE_NAME = `prometheusCode_${Date.now()}`;
await navigateToEditor(page, selectors, createDataSource, 'Code', DATASOURCE_NAME, gotoDataSourceConfigPage);
await getResources(page); await getResources(page);
@ -217,12 +258,24 @@ test.describe.skip(
}); });
test.describe('Query builder', () => { test.describe('Query builder', () => {
test('navigates to the query builder with editor type as code', async ({ page, selectors }) => { test('navigates to the query builder with editor type as code', async ({
await navigateToEditor(page, selectors, 'Builder', 'prometheusBuilder'); createDataSource,
gotoDataSourceConfigPage,
page,
selectors,
}) => {
const DATASOURCE_NAME = `prometheusBuilder_${Date.now()}`;
await navigateToEditor(page, selectors, createDataSource, 'Builder', DATASOURCE_NAME, gotoDataSourceConfigPage);
}); });
test('the query builder contains metric select, label filters and operations', async ({ page, selectors }) => { test('the query builder contains metric select, label filters and operations', async ({
await navigateToEditor(page, selectors, 'Builder', 'prometheusBuilder'); createDataSource,
gotoDataSourceConfigPage,
page,
selectors,
}) => {
const DATASOURCE_NAME = `prometheusBuilder_${Date.now()}`;
await navigateToEditor(page, selectors, createDataSource, 'Builder', DATASOURCE_NAME, gotoDataSourceConfigPage);
await getResources(page); await getResources(page);
@ -241,8 +294,15 @@ test.describe.skip(
await expect(valueSelect).toBeVisible(); await expect(valueSelect).toBeVisible();
}); });
test('can select a metric and provide a hint', async ({ page, selectors }) => { // this throws a maximum update depth error?!
await navigateToEditor(page, selectors, 'Builder', 'prometheusBuilder'); test.skip('can select a metric and provide a hint', async ({
createDataSource,
gotoDataSourceConfigPage,
page,
selectors,
}) => {
const DATASOURCE_NAME = `prometheusBuilder_${Date.now()}`;
await navigateToEditor(page, selectors, createDataSource, 'Builder', DATASOURCE_NAME, gotoDataSourceConfigPage);
await getResources(page); await getResources(page);
@ -258,8 +318,14 @@ test.describe.skip(
await expect(hints).toContainText('hint: add rate'); await expect(hints).toContainText('hint: add rate');
}); });
test('should have the metrics explorer opened via the metric select', async ({ page, selectors }) => { test('should have the metrics explorer opened via the metric select', async ({
await navigateToEditor(page, selectors, 'Builder', 'prometheusBuilder'); createDataSource,
gotoDataSourceConfigPage,
page,
selectors,
}) => {
const DATASOURCE_NAME = `prometheusBuilder_${Date.now()}`;
await navigateToEditor(page, selectors, createDataSource, 'Builder', DATASOURCE_NAME, gotoDataSourceConfigPage);
await getResources(page); await getResources(page);
@ -267,9 +333,8 @@ test.describe.skip(
selectors.components.DataSource.Prometheus.queryEditor.builder.metricSelect selectors.components.DataSource.Prometheus.queryEditor.builder.metricSelect
); );
await expect(metricSelect).toBeVisible(); await expect(metricSelect).toBeVisible();
await metricSelect.click();
await selectOption(page, 'Metrics explorer', selectors); await page.getByLabel('Open metrics explorer').click();
const metricsExplorer = page.getByTestId( const metricsExplorer = page.getByTestId(
selectors.components.DataSource.Prometheus.queryEditor.builder.metricsExplorer selectors.components.DataSource.Prometheus.queryEditor.builder.metricsExplorer

View File

@ -1,20 +1,22 @@
import { test, expect } from '@grafana/plugin-e2e'; import { Page } from 'playwright-core';
import { test, expect, E2ESelectorGroups } from '@grafana/plugin-e2e';
import { addDashboard } from '../utils/dashboard-helpers'; import { addDashboard } from '../utils/dashboard-helpers';
import { getResources } from '../utils/prometheus-helpers'; import { getResources } from '../utils/prometheus-helpers';
test.describe.skip( test.describe(
'Prometheus variable query editor', 'Prometheus variable query editor',
{ {
tag: ['@various', '@wip'], tag: ['@various'],
}, },
() => { () => {
const DATASOURCE_NAME = 'prometheusVariableDS'; const DATASOURCE_PREFIX = 'prometheusVariableDS';
/** /**
* Click dashboard settings and then the variables tab * Click dashboard settings and then the variables tab
*/ */
async function navigateToVariables(page, selectors) { async function navigateToVariables(page: Page, selectors: E2ESelectorGroups) {
const editButton = page.getByTestId(selectors.components.NavToolbar.editDashboard.editButton); const editButton = page.getByTestId(selectors.components.NavToolbar.editDashboard.editButton);
await expect(editButton).toBeVisible(); await expect(editButton).toBeVisible();
await editButton.click(); await editButton.click();
@ -30,7 +32,12 @@ test.describe.skip(
/** /**
* Begin the process of adding a query type variable for a Prometheus data source * Begin the process of adding a query type variable for a Prometheus data source
*/ */
async function addPrometheusQueryVariable(page, selectors, variableName) { async function addPrometheusQueryVariable(
page: Page,
selectors: E2ESelectorGroups,
datasourceName: string,
variableName: string
) {
const addVariableButton = page.getByTestId(selectors.pages.Dashboard.Settings.Variables.List.addVariableCTAV2); const addVariableButton = page.getByTestId(selectors.pages.Dashboard.Settings.Variables.List.addVariableCTAV2);
await addVariableButton.click(); await addVariableButton.click();
@ -42,7 +49,7 @@ test.describe.skip(
await expect(dataSourcePicker).toBeVisible(); await expect(dataSourcePicker).toBeVisible();
await dataSourcePicker.click(); await dataSourcePicker.click();
const dataSourceOption = page.getByText(DATASOURCE_NAME); const dataSourceOption = page.getByText(datasourceName);
await dataSourceOption.scrollIntoViewIfNeeded(); await dataSourceOption.scrollIntoViewIfNeeded();
await expect(dataSourceOption).toBeVisible(); await expect(dataSourceOption).toBeVisible();
await dataSourceOption.click(); await dataSourceOption.click();
@ -53,10 +60,16 @@ test.describe.skip(
/** /**
* Create a Prometheus variable and navigate to the query editor to check that it is available to use. * Create a Prometheus variable and navigate to the query editor to check that it is available to use.
*/ */
async function variableFlowToQueryEditor(page, selectors, variableName, queryType) { async function variableFlowToQueryEditor(
page: Page,
selectors: E2ESelectorGroups,
datasourceName: string,
variableName: string,
queryType: string
) {
await addDashboard(page); await addDashboard(page);
await navigateToVariables(page, selectors); await navigateToVariables(page, selectors);
await addPrometheusQueryVariable(page, selectors, variableName); await addPrometheusQueryVariable(page, selectors, datasourceName, variableName);
// Select query type // Select query type
const queryTypeSelect = page.getByTestId( const queryTypeSelect = page.getByTestId(
@ -88,7 +101,7 @@ test.describe.skip(
// Select prom data source from the data source list // Select prom data source from the data source list
const dataSourcePickerInput = page.getByTestId(selectors.components.DataSourcePicker.inputV2); const dataSourcePickerInput = page.getByTestId(selectors.components.DataSourcePicker.inputV2);
await dataSourcePickerInput.click(); await dataSourcePickerInput.click();
await dataSourcePickerInput.fill(DATASOURCE_NAME); await dataSourcePickerInput.fill(datasourceName);
await page.keyboard.press('Enter'); await page.keyboard.press('Enter');
// Confirm the variable exists in the correct input // Confirm the variable exists in the correct input
@ -119,19 +132,21 @@ test.describe.skip(
} }
} }
test.beforeEach(async ({ page, selectors, createDataSourceConfigPage }) => {
await createDataSourceConfigPage({ type: 'prometheus', name: DATASOURCE_NAME });
});
test('should navigate to variable query editor', async ({ page, selectors }) => { test('should navigate to variable query editor', async ({ page, selectors }) => {
await addDashboard(page); await addDashboard(page);
await navigateToVariables(page, selectors); await navigateToVariables(page, selectors);
}); });
test('should select a query type for a Prometheus variable query', async ({ page, selectors }) => { test('should select a query type for a Prometheus variable query', async ({
createDataSource,
page,
selectors,
}) => {
const DATASOURCE_NAME = `${DATASOURCE_PREFIX}_${Date.now()}`;
await createDataSource({ type: 'prometheus', name: DATASOURCE_NAME });
await addDashboard(page); await addDashboard(page);
await navigateToVariables(page, selectors); await navigateToVariables(page, selectors);
await addPrometheusQueryVariable(page, selectors, 'labelsVariable'); await addPrometheusQueryVariable(page, selectors, DATASOURCE_NAME, 'labelsVariable');
// Select query type // Select query type
const queryTypeSelect = page.getByTestId( const queryTypeSelect = page.getByTestId(
@ -142,29 +157,38 @@ test.describe.skip(
}); });
test('should create a label names variable that is selectable in the label select in query builder', async ({ test('should create a label names variable that is selectable in the label select in query builder', async ({
createDataSource,
page, page,
selectors, selectors,
}) => { }) => {
await variableFlowToQueryEditor(page, selectors, 'labelnames', 'Label names'); const DATASOURCE_NAME = `${DATASOURCE_PREFIX}_${Date.now()}`;
await createDataSource({ type: 'prometheus', name: DATASOURCE_NAME });
await variableFlowToQueryEditor(page, selectors, DATASOURCE_NAME, 'labelnames', 'Label names');
}); });
test('should create a label values variable that is selectable in the label values select in query builder', async ({ test('should create a label values variable that is selectable in the label values select in query builder', async ({
createDataSource,
page, page,
selectors, selectors,
}) => { }) => {
await variableFlowToQueryEditor(page, selectors, 'labelvalues', 'Label values'); const DATASOURCE_NAME = `${DATASOURCE_PREFIX}_${Date.now()}`;
await createDataSource({ type: 'prometheus', name: DATASOURCE_NAME });
await variableFlowToQueryEditor(page, selectors, DATASOURCE_NAME, 'labelvalues', 'Label values');
}); });
test('should create a metric names variable that is selectable in the metric select in query builder', async ({ test('should create a metric names variable that is selectable in the metric select in query builder', async ({
createDataSource,
page, page,
selectors, selectors,
}) => { }) => {
await variableFlowToQueryEditor(page, selectors, 'metrics', 'Metrics'); const DATASOURCE_NAME = `${DATASOURCE_PREFIX}_${Date.now()}`;
await createDataSource({ type: 'prometheus', name: DATASOURCE_NAME });
await variableFlowToQueryEditor(page, selectors, DATASOURCE_NAME, 'metrics', 'Metrics');
}); });
} }
); );
async function selectOption(page, option) { async function selectOption(page: Page, option: string) {
const optionElement = page.getByRole('option', { name: option }); const optionElement = page.getByRole('option', { name: option });
await expect(optionElement).toBeVisible(); await expect(optionElement).toBeVisible();
await optionElement.click(); await optionElement.click();

View File

@ -1,27 +1,21 @@
import { readFileSync } from 'fs';
import { join } from 'path';
import { test, expect } from '@grafana/plugin-e2e'; import { test, expect } from '@grafana/plugin-e2e';
import longTraceResponse from '../fixtures/long-trace-response.json';
// this test requires a larger viewport // this test requires a larger viewport
test.use({ test.use({
viewport: { width: 1280, height: 1080 }, viewport: { width: 1280, height: 1080 },
}); });
// TODO for some reason, this test gives "connection refused" errors in CI test.describe(
test.describe.skip(
'Trace view', 'Trace view',
{ {
tag: ['@various'], tag: ['@various'],
}, },
() => { () => {
test('Can lazy load big traces', async ({ page, selectors }) => { test('Can lazy load big traces', async ({ page, selectors }) => {
// Load the fixture data
const fixturePath = join(__dirname, '../fixtures/long-trace-response.json');
const longTraceResponse = JSON.parse(readFileSync(fixturePath, 'utf8'));
// Mock the API response // Mock the API response
await page.route('*/**/api/traces/trace', async (route) => { await page.route('**/api/ds/query?ds_type=jaeger*', async (route) => {
await route.fulfill({ await route.fulfill({
status: 200, status: 200,
contentType: 'application/json', contentType: 'application/json',

View File

@ -1,155 +0,0 @@
import { random } from 'lodash';
import { e2e } from '../utils';
const DATASOURCE_ID = 'sandbox-test-datasource';
let DATASOURCE_CONNECTION_ID = '';
const DATASOURCE_TYPED_NAME = 'SandboxDatasourceInstance';
// Skipping due to flakiness/race conditions with same old arch test e2e/various-suite/frontend-sandbox-datasource.spec.ts
describe('Datasource sandbox', () => {
before(() => {
e2e.flows.login(Cypress.env('USERNAME'), Cypress.env('PASSWORD'), true);
e2e.pages.AddDataSource.visit();
e2e.pages.AddDataSource.dataSourcePluginsV2('Sandbox datasource test plugin')
.scrollIntoView()
.should('be.visible') // prevents flakiness
.click();
e2e.pages.DataSource.name().clear();
e2e.pages.DataSource.name().type(DATASOURCE_TYPED_NAME);
e2e.pages.DataSource.saveAndTest().click();
cy.url().then((url) => {
const split = url.split('/');
DATASOURCE_CONNECTION_ID = split[split.length - 1];
});
});
beforeEach(() => {
e2e.flows.login(Cypress.env('USERNAME'), Cypress.env('PASSWORD'), true);
});
describe('Config Editor', () => {
describe('Sandbox disabled', () => {
beforeEach(() => {
cy.window().then((win) => {
win.localStorage.setItem('grafana.featureToggles', 'pluginsFrontendSandbox=0');
});
});
it('Should not render a sandbox wrapper around the datasource config editor', () => {
e2e.pages.EditDataSource.visit(DATASOURCE_CONNECTION_ID);
cy.wait(300); // wait to prevent false positives because cypress checks too fast
cy.get(`div[data-plugin-sandbox="${DATASOURCE_ID}"]`).should('not.exist');
});
});
describe('Sandbox enabled', () => {
beforeEach(() => {
cy.window().then((win) => {
win.localStorage.setItem('grafana.featureToggles', 'pluginsFrontendSandbox=1');
});
});
it('Should render a sandbox wrapper around the datasource config editor', () => {
e2e.pages.EditDataSource.visit(DATASOURCE_CONNECTION_ID);
cy.get(`div[data-plugin-sandbox="${DATASOURCE_ID}"]`).should('exist');
});
it('Should store values in jsonData and secureJsonData correctly', () => {
e2e.pages.EditDataSource.visit(DATASOURCE_CONNECTION_ID);
const valueToStore = 'test' + random(100);
cy.get('[data-testid="sandbox-config-editor-query-input"]').should('not.be.disabled');
cy.get('[data-testid="sandbox-config-editor-query-input"]').type(valueToStore);
cy.get('[data-testid="sandbox-config-editor-query-input"]').should('have.value', valueToStore);
e2e.pages.DataSource.saveAndTest().click();
e2e.pages.DataSource.alert().should('exist').contains('Sandbox Success', {});
// validate the value was stored
e2e.pages.EditDataSource.visit(DATASOURCE_CONNECTION_ID);
cy.get('[data-testid="sandbox-config-editor-query-input"]').should('not.be.disabled');
cy.get('[data-testid="sandbox-config-editor-query-input"]').should('have.value', valueToStore);
});
});
});
describe('Explore Page', () => {
describe('Sandbox disabled', () => {
beforeEach(() => {
cy.window().then((win) => {
win.localStorage.setItem('grafana.featureToggles', 'pluginsFrontendSandbox=0');
});
});
it('Should not wrap the query editor in a sandbox wrapper', () => {
e2e.pages.Explore.visit();
e2e.components.DataSourcePicker.container().should('be.visible').click();
cy.contains(DATASOURCE_TYPED_NAME).scrollIntoView().should('be.visible').click();
// make sure the datasource was correctly selected and rendered
e2e.components.Breadcrumbs.breadcrumb(DATASOURCE_TYPED_NAME).should('be.visible');
cy.wait(300); // wait to prevent false positives because cypress checks too fast
cy.get(`div[data-plugin-sandbox="${DATASOURCE_ID}"]`).should('not.exist');
});
it('Should accept values when typed', () => {
e2e.pages.Explore.visit();
e2e.components.DataSourcePicker.container().should('be.visible').click();
cy.contains(DATASOURCE_TYPED_NAME).scrollIntoView().should('be.visible').click();
// make sure the datasource was correctly selected and rendered
e2e.components.Breadcrumbs.breadcrumb(DATASOURCE_TYPED_NAME).should('be.visible');
const valueToType = 'test' + random(100);
cy.get('[data-testid="sandbox-query-editor-query-input"]').should('not.be.disabled');
cy.get('[data-testid="sandbox-query-editor-query-input"]').type(valueToType);
cy.get('[data-testid="sandbox-query-editor-query-input"]').should('have.value', valueToType);
});
});
describe('Sandbox enabled', () => {
beforeEach(() => {
cy.window().then((win) => {
win.localStorage.setItem('grafana.featureToggles', 'pluginsFrontendSandbox=1');
});
});
it('Should wrap the query editor in a sandbox wrapper', () => {
e2e.pages.Explore.visit();
e2e.components.DataSourcePicker.container().should('be.visible').click();
cy.contains(DATASOURCE_TYPED_NAME).scrollIntoView().should('be.visible').click();
// make sure the datasource was correctly selected and rendered
e2e.components.Breadcrumbs.breadcrumb(DATASOURCE_TYPED_NAME).should('be.visible');
cy.get(`div[data-plugin-sandbox="${DATASOURCE_ID}"]`).should('exist');
});
it('Should accept values when typed', () => {
e2e.pages.Explore.visit();
e2e.components.DataSourcePicker.container().should('be.visible').click();
cy.contains(DATASOURCE_TYPED_NAME).scrollIntoView().should('be.visible').click();
// make sure the datasource was correctly selected and rendered
e2e.components.Breadcrumbs.breadcrumb(DATASOURCE_TYPED_NAME).should('be.visible');
const valueToType = 'test' + random(100);
cy.get('[data-testid="sandbox-query-editor-query-input"]').should('not.be.disabled');
cy.get('[data-testid="sandbox-query-editor-query-input"]').type(valueToType);
cy.get('[data-testid="sandbox-query-editor-query-input"]').should('have.value', valueToType);
// typing the query editor should reflect in the url
cy.url().should('include', valueToType);
});
});
});
afterEach(() => {
e2e.flows.revertAllChanges();
});
after(() => {
cy.clearCookies();
});
});

View File

@ -1,62 +0,0 @@
import { e2e } from '../../utils';
/**
* Create a Prom data source
*/
export function createPromDS(dataSourceID: string, name: string): void {
// login
e2e.flows.login(Cypress.env('USERNAME'), Cypress.env('PASSWORD'), true);
// select the prometheus DS
e2e.pages.AddDataSource.visit();
e2e.pages.AddDataSource.dataSourcePluginsV2(dataSourceID)
.scrollIntoView()
.should('be.visible') // prevents flakiness
.click();
// add url for DS to save without error
e2e.components.DataSource.Prometheus.configPage.connectionSettings().type('http://prom-url:9090');
// name the DS
e2e.pages.DataSource.name().clear();
e2e.pages.DataSource.name().type(name);
e2e.pages.DataSource.saveAndTest().click();
}
export function getResources() {
cy.intercept(/__name__/g, metricResponse);
cy.intercept(/metadata/g, metadataResponse);
cy.intercept(/labels/g, labelsResponse);
}
const metricResponse = {
status: 'success',
data: ['metric1', 'metric2'],
};
const metadataResponse = {
status: 'success',
data: {
metric1: [
{
type: 'counter',
help: 'metric1 help',
unit: '',
},
],
metric2: [
{
type: 'counter',
help: 'metric2 help',
unit: '',
},
],
},
};
const labelsResponse = {
status: 'success',
data: ['__name__', 'action', 'active', 'backend'],
};

View File

@ -1,353 +0,0 @@
import { e2e } from '../utils';
describe('Migrate to Cloud (On-prem)', () => {
// Here we are mostly testing the UI flow and can do interesting things with the backend responses to see how the UI behaves.
describe('with mocked calls to the API backend', () => {
afterEach(() => {
cy.get('[data-testid="migrate-to-cloud-summary-disconnect-button"]').should('be.visible').click();
});
const SESSION_UID = 'fehq6hqd246iox';
const SNAPSHOT_UID1 = 'cehq6vdjqbqbkx';
const SNAPSHOT_RESULTS = [
{ name: 'FolderA', type: 'FOLDER', refId: 'ref-id-folder-a', parentName: 'General' },
{ name: 'FolderB', type: 'FOLDER', refId: 'ref-id-folder-b', parentName: 'General' },
{ name: 'Prometheus', type: 'DATASOURCE', refId: 'prometheus' },
{ name: 'Postgres', type: 'DATASOURCE', refId: 'postgres' },
{ name: 'Loki', type: 'DATASOURCE', refId: 'loki' },
{ name: 'Alert Rule A', type: 'ALERT_RULE', refId: 'alert-rule-a', parentName: 'FolderA' },
{ name: 'Alert Rule B', type: 'ALERT_RULE', refId: 'alert-rule-b', parentName: 'FolderB' },
{ name: 'Alert Rule C', type: 'ALERT_RULE', refId: 'alert-rule-c', parentName: 'FolderB' },
{ name: 'Alert Rule Group A', type: 'ALERT_RULE_GROUP', refId: 'alert-rule-group-a', parentName: 'FolderA' },
{ name: 'Alert Rule Group B', type: 'ALERT_RULE_GROUP', refId: 'alert-rule-group-b', parentName: 'FolderB' },
{ name: 'Contact Point A', type: 'CONTACT_POINT', refId: 'contact-point-a' },
{ name: 'Contact Point B', type: 'CONTACT_POINT', refId: 'contact-point-b' },
{ name: 'Contact Point C', type: 'CONTACT_POINT', refId: 'contact-point-c' },
{ name: 'Notification Policy A', type: 'NOTIFICATION_POLICY', refId: 'notification-policy-a' },
{ name: 'Notification Template A', type: 'NOTIFICATION_TEMPLATE', refId: 'notification-template-a' },
{ name: 'Notification Template B', type: 'NOTIFICATION_TEMPLATE', refId: 'notification-template-b' },
{ name: 'Notification Template C', type: 'NOTIFICATION_TEMPLATE', refId: 'notification-template-c' },
{ name: 'Plugin A', type: 'PLUGIN', refId: 'plugin-a' },
{ name: 'Plugin B', type: 'PLUGIN', refId: 'plugin-b' },
{ name: 'Plugin C', type: 'PLUGIN', refId: 'plugin-c' },
{ name: 'Mute Timing A', type: 'MUTE_TIMING', refId: 'mute-timing-a' },
{ name: 'Mute Timing B', type: 'MUTE_TIMING', refId: 'mute-timing-b' },
];
const MIGRATION_SESSION = {
uid: SESSION_UID,
slug: 'test-slug',
created: '2025-04-02T21:36:08+02:00',
updated: '2025-04-02T21:36:08+02:00',
};
const STATS = {
types: SNAPSHOT_RESULTS.reduce(
(acc, r) => {
acc[r.type] = (acc[r.type] || 0) + 1;
return acc;
},
{} as Record<string, number>
),
statuses: {
PENDING: SNAPSHOT_RESULTS.length,
},
total: SNAPSHOT_RESULTS.length,
};
it('creates and uploads a snapshot sucessfully', () => {
// Login using the UI.
e2e.flows.login(Cypress.env('USERNAME'), Cypress.env('PASSWORD'));
// Visit the migrate to cloud onprem page.
e2e.pages.MigrateToCloud.visit();
// Open the connect modal and enter the token.
cy.get('[data-testid="migrate-to-cloud-connect-session-modal-button"]').should('be.visible').click();
cy.get('[data-testid="migrate-to-cloud-connect-session-modal-token-input"]')
.should('be.visible')
.focus()
.type('test');
cy.intercept('POST', '/api/cloudmigration/migration', {
statusCode: 200,
body: MIGRATION_SESSION,
}).as('createMigrationToken');
cy.intercept('GET', '/api/cloudmigration/migration', {
statusCode: 200,
body: {
sessions: [MIGRATION_SESSION],
},
}).as('getMigrationSessionList');
cy.intercept('GET', `/api/cloudmigration/migration/${SESSION_UID}/snapshots?page=1&limit=1*`, {
statusCode: 200,
body: {
snapshots: [],
},
}).as('getSnapshotListInitial');
// Click the connect button to create the token.
cy.get('[data-testid="migrate-to-cloud-connect-session-modal-connect-button"]').should('be.visible').click();
// Wait for the token to be created and the migration session list to be fetched to kickstart the UI state machine.
cy.wait(['@createMigrationToken', '@getMigrationSessionList', '@getSnapshotListInitial']);
// Check the 'Include all' resources checkbox.
cy.get('[data-testid="migrate-to-cloud-configure-snapshot-checkbox-resource-include-all"]')
.check({ force: true })
.should('be.checked');
// And validate that all resources are indeed checked.
for (const resourceType of [
'alert_rule',
'alert_rule_group',
'contact_point',
'dashboard',
'datasource',
'folder',
'library_element',
'mute_timing',
'notification_policy',
'notification_template',
'plugin',
]) {
cy.get(
`[data-testid="migrate-to-cloud-configure-snapshot-checkbox-resource-${resourceType.toLowerCase()}"]`
).should('be.checked');
}
// Remove one of the resources that has dependencies.
// Mute Timings are dependencies of Alert Rules, which are dependencies of Alert Rule Groups.
cy.get(`[data-testid="migrate-to-cloud-configure-snapshot-checkbox-resource-mute_timing"]`)
.uncheck({ force: true })
.should('not.be.checked');
// Validate that those resources are now unchecked.
for (const resourceType of ['alert_rule', 'alert_rule_group', 'include-all']) {
cy.get(
`[data-testid="migrate-to-cloud-configure-snapshot-checkbox-resource-${resourceType.toLowerCase()}"]`
).should('not.be.checked');
}
// Check everything again because we can.
cy.get('[data-testid="migrate-to-cloud-configure-snapshot-checkbox-resource-include-all"]')
.check({ force: true })
.should('be.checked');
// Validate that those resources are now checked again.
for (const resourceType of ['alert_rule', 'alert_rule_group', 'mute_timing']) {
cy.get(
`[data-testid="migrate-to-cloud-configure-snapshot-checkbox-resource-${resourceType.toLowerCase()}"]`
).should('be.checked');
}
cy.intercept('POST', `/api/cloudmigration/migration/${SESSION_UID}/snapshot`, {
statusCode: 200,
body: {
uid: SNAPSHOT_UID1,
},
}).as('createSnapshot');
cy.intercept('GET', `/api/cloudmigration/migration/${SESSION_UID}/snapshots?page=1&limit=1*`, {
statusCode: 200,
body: {
snapshots: [
{
uid: SNAPSHOT_UID1,
sessionUid: SESSION_UID,
status: 'CREATING',
created: '2025-04-02T21:40:23+02:00',
finished: '0001-01-01T00:00:00Z',
},
],
},
}).as('getSnapshotListCreating');
let getSnapshotCalled = false;
cy.intercept(
'GET',
`/api/cloudmigration/migration/${SESSION_UID}/snapshot/${SNAPSHOT_UID1}?resultPage=1&resultLimit=50*`,
(req) => {
if (!getSnapshotCalled) {
getSnapshotCalled = true;
req.reply((res) => {
res.send({
statusCode: 200,
body: {
uid: SNAPSHOT_UID1,
sessionUid: SESSION_UID,
status: 'CREATING',
created: '2025-04-02T21:40:23+02:00',
finished: '0001-01-01T00:00:00Z',
results: [],
stats: {
types: {},
statuses: {},
total: 0,
},
},
});
});
} else {
req.reply((res) => {
res.send({
statusCode: 200,
body: {
uid: SNAPSHOT_UID1,
sessionUid: SESSION_UID,
status: 'PENDING_UPLOAD',
created: '2025-04-02T21:40:23+02:00',
finished: '0001-01-01T00:00:00Z',
results: SNAPSHOT_RESULTS.map((r) => ({ ...r, status: 'PENDING' })),
stats: STATS,
},
});
});
}
}
).as('getSnapshot');
// Build the snapshot.
cy.get('[data-testid="migrate-to-cloud-configure-snapshot-build-snapshot-button"]').should('be.visible').click();
// Wait for the snapshot to be created. Simulate it going from INITIALIZING to PENDING_UPLOAD.
cy.wait(['@createSnapshot', '@getSnapshotListCreating', '@getSnapshot']);
cy.intercept('POST', `/api/cloudmigration/migration/${SESSION_UID}/snapshot/${SNAPSHOT_UID1}/upload`, {
statusCode: 200,
}).as('uploadSnapshot');
// Upload the snapshot.
cy.get('[data-testid="migrate-to-cloud-summary-upload-snapshot-button"]')
.should('be.visible')
.wait(2000)
.focus()
.trigger('click', { force: true, waitForAnimations: true });
cy.intercept('GET', `/api/cloudmigration/migration/${SESSION_UID}/snapshots?page=1&limit=1*`, {
statusCode: 200,
body: {
snapshots: [
{
uid: SNAPSHOT_UID1,
sessionUid: SESSION_UID,
status: 'UPLOADING',
created: '2025-04-02T21:40:23+02:00',
finished: '0001-01-01T00:00:00Z',
},
],
},
}).as('getSnapshotListUploading');
// Simulate the snapshot being uploaded, the frontend will keep polling until the snapshot is either finished or errored.
let getSnapshotUploadingCalls = 0;
cy.intercept(
'GET',
`/api/cloudmigration/migration/${SESSION_UID}/snapshot/${SNAPSHOT_UID1}?resultPage=1&resultLimit=50*`,
(req) => {
req.reply((res) => {
if (getSnapshotUploadingCalls <= 1) {
res.send({
statusCode: 200,
body: {
uid: SNAPSHOT_UID1,
sessionUid: SESSION_UID,
status: getSnapshotUploadingCalls === 1 ? 'PROCESSING' : 'UPLOADING',
created: '2025-04-02T21:40:23+02:00',
finished: '0001-01-01T00:00:00Z',
results: SNAPSHOT_RESULTS.map((r) => ({ ...r, status: 'PENDING' })),
stats: STATS,
},
});
getSnapshotUploadingCalls++;
} else {
res.send({
statusCode: 200,
body: {
uid: SNAPSHOT_UID1,
sessionUid: SESSION_UID,
status: 'FINISHED',
created: '2025-03-27T12:00:00Z',
finished: '2025-03-27T12:00:00Z',
results: SNAPSHOT_RESULTS.map((r) => ({ ...r, status: 'OK' })),
stats: {
types: STATS.types,
statuses: SNAPSHOT_RESULTS.reduce(
(acc, r) => {
const status = (r as { status?: string }).status || 'UNKNOWN';
acc[status] = (acc[status] || 0) + 1;
return acc;
},
{} as Record<string, number>
),
total: SNAPSHOT_RESULTS.length,
},
},
});
}
});
}
).as('getSnapshotUploading');
// Wait for the request to kickstart the upload and then wait until it is finished.
cy.wait(['@uploadSnapshot', '@getSnapshotListUploading', '@getSnapshotUploading']);
// At least some of the items are marked with "Uploaded to cloud" status.
cy.contains('Uploaded to cloud').should('be.visible');
// We can now reconfigure the snapshot.
cy.get('[data-testid="migrate-to-cloud-summary-reconfigure-snapshot-button"]').should('be.visible').click();
// Check the 'Include all' resources checkbox.
cy.get('[data-testid="migrate-to-cloud-configure-snapshot-checkbox-resource-include-all"]')
.check({ force: true })
.should('be.checked');
});
});
// Here we are doing a more black box testing of the migration flow, without explicitly mocking the API calls,
// but we instead rely on the `[cloud_migration] developer_mode = true` to be set in the `custom.ini` file,
// which will make the service use in-memory fake implementations of 3rdparty dependencies, but we'll still
// use the real API endpoints, database and business logic.
describe('with a fake GMS backend implementation', () => {
afterEach(() => {
cy.get('[data-testid="migrate-to-cloud-summary-disconnect-button"]').should('be.visible').click();
});
// Manually crafted base64 token for testing, does not contain any sensitive data.
const TEST_TOKEN =
'eyJUb2tlbiI6ImdsY19kZXZfZXlKdklqb2lNVEl6TkNJc0ltNGlPaUpuY21GbVlXNWhMV05zYjNWa0xXMXBaM0poZEdsdmJuTXRNVEl6TkNJc0ltc2lPaUowWlhOMElpd2liU0k2ZXlKeUlqb2laR1YyTFhWekxXTmxiblJ5WVd3aWZYMEsiLCJJbnN0YW5jZSI6eyJTdGFja0lEIjoxMjM0LCJTbHVnIjoidGVzdC1zbHVnIiwiUmVnaW9uU2x1ZyI6ImRldi11cy1jZW50cmFsIiwiQ2x1c3RlclNsdWciOiJkZXYtdXMtY2VudHJhbC0wIn19Cg==';
it('creates a snapshot sucessfully', () => {
// Login using the UI.
e2e.flows.login(Cypress.env('USERNAME'), Cypress.env('PASSWORD'));
// Visit the migrate to cloud onprem page.
e2e.pages.MigrateToCloud.visit();
// Open the connect modal and enter the token.
cy.get('[data-testid="migrate-to-cloud-connect-session-modal-button"]').should('be.visible').click();
cy.get('[data-testid="migrate-to-cloud-connect-session-modal-token-input"]')
.should('be.visible')
.focus()
.type(TEST_TOKEN);
// Click the connect button to create the token.
cy.get('[data-testid="migrate-to-cloud-connect-session-modal-connect-button"]').should('be.visible').click();
// Build the snapshot.
cy.get('[data-testid="migrate-to-cloud-configure-snapshot-build-snapshot-button"]').should('be.visible').click();
// And the rebuild button should be visible.
cy.get('[data-testid="migrate-to-cloud-summary-reconfigure-snapshot-button"]').should('be.visible');
// We don't upload the snapshot yet because we need to create a mock server to validate the uploaded items,
// similarly to what the SMTP (tester) server does.
});
});
});

View File

@ -1,111 +0,0 @@
import { selectors } from '@grafana/e2e-selectors';
import { e2e } from '../utils';
const DATASOURCE_ID = 'Prometheus';
const DATASOURCE_TYPED_NAME = 'PrometheusDatasourceInstance';
describe('Prometheus config', () => {
beforeEach(() => {
e2e.flows.login(Cypress.env('USERNAME'), Cypress.env('PASSWORD'), true);
e2e.pages.AddDataSource.visit();
e2e.pages.AddDataSource.dataSourcePluginsV2(DATASOURCE_ID)
.scrollIntoView()
.should('be.visible') // prevents flakiness
.click({ force: true });
});
it(`should have the following components:
connection settings
managed alerts
scrape interval
query timeout
default editor
disable metric lookup
prometheus type
cache level
incremental querying
disable recording rules
custom query parameters
http method
`, () => {
// connection settings
e2e.components.DataSource.Prometheus.configPage.connectionSettings().should('be.visible');
// managed alerts
cy.get(`#${selectors.components.DataSource.Prometheus.configPage.manageAlerts}`).scrollIntoView().should('exist');
// scrape interval
e2e.components.DataSource.Prometheus.configPage.scrapeInterval().scrollIntoView().should('exist');
// query timeout
e2e.components.DataSource.Prometheus.configPage.queryTimeout().scrollIntoView().should('exist');
// default editor
e2e.components.DataSource.Prometheus.configPage.defaultEditor().scrollIntoView().should('exist');
// disable metric lookup
cy.get(`#${selectors.components.DataSource.Prometheus.configPage.disableMetricLookup}`)
.scrollIntoView()
.should('exist');
// prometheus type
e2e.components.DataSource.Prometheus.configPage.prometheusType().scrollIntoView().should('exist');
// cache level
e2e.components.DataSource.Prometheus.configPage.cacheLevel().scrollIntoView().should('exist');
// incremental querying
cy.get(`#${selectors.components.DataSource.Prometheus.configPage.incrementalQuerying}`)
.scrollIntoView()
.should('exist');
// disable recording rules
cy.get(`#${selectors.components.DataSource.Prometheus.configPage.disableRecordingRules}`)
.scrollIntoView()
.should('exist');
// custom query parameters
e2e.components.DataSource.Prometheus.configPage.customQueryParameters().scrollIntoView().should('exist');
// http method
e2e.components.DataSource.Prometheus.configPage.httpMethod().scrollIntoView().should('exist');
});
it('should save the default editor when navigating to explore', () => {
e2e.components.DataSource.Prometheus.configPage.defaultEditor().scrollIntoView().should('exist').click();
selectOption('Builder');
e2e.components.DataSource.Prometheus.configPage.connectionSettings().type('http://prom-url:9090');
e2e.pages.DataSource.name().clear();
e2e.pages.DataSource.name().type(DATASOURCE_TYPED_NAME);
e2e.pages.DataSource.saveAndTest().click();
e2e.pages.Explore.visit();
e2e.components.DataSourcePicker.container().should('be.visible').click();
e2e.components.DataSourcePicker.container().type(`${DATASOURCE_TYPED_NAME}{enter}`);
e2e.components.DataSource.Prometheus.queryEditor.builder.metricSelect().should('exist');
});
it('should allow a user to add the version when the Prom type is selected', () => {
e2e.components.DataSource.Prometheus.configPage.prometheusType().scrollIntoView().should('exist').click();
selectOption('Prometheus');
e2e.components.DataSource.Prometheus.configPage.prometheusVersion().scrollIntoView().should('exist');
});
it('should have a cache level component', () => {
e2e.components.DataSource.Prometheus.configPage.cacheLevel().scrollIntoView().should('exist');
});
it('should allow a user to select a query overlap window when incremental querying is selected', () => {
cy.get(`#${selectors.components.DataSource.Prometheus.configPage.incrementalQuerying}`)
.scrollIntoView()
.should('exist')
.check({ force: true });
e2e.components.DataSource.Prometheus.configPage.queryOverlapWindow().scrollIntoView().should('exist');
});
// exemplars tested in exemplar.spec
});
export function selectOption(option: string) {
e2e.components.Select.option().contains(option).should('be.visible').click();
}

View File

@ -1,167 +0,0 @@
import { e2e } from '../utils';
import { getResources } from './helpers/prometheus-helpers';
const DATASOURCE_ID = 'Prometheus';
type editorType = 'Code' | 'Builder';
/**
* Login, create and save a Prometheus data source, navigate to code or builder
*
* @param editorType 'Code' or 'Builder'
*/
function navigateToEditor(editorType: editorType, name: string): void {
// login
e2e.flows.login(Cypress.env('USERNAME'), Cypress.env('PASSWORD'), true);
// select the prometheus DS
e2e.pages.AddDataSource.visit();
e2e.pages.AddDataSource.dataSourcePluginsV2(DATASOURCE_ID)
.scrollIntoView()
.should('be.visible') // prevents flakiness
.click();
// choose default editor
e2e.components.DataSource.Prometheus.configPage.defaultEditor().scrollIntoView().should('exist').click();
selectOption(editorType);
// add url for DS to save without error
e2e.components.DataSource.Prometheus.configPage.connectionSettings().type('http://prom-url:9090');
// name the DS
e2e.pages.DataSource.name().clear();
e2e.pages.DataSource.name().type(name);
e2e.pages.DataSource.saveAndTest().click();
// visit explore
e2e.pages.Explore.visit();
// choose the right DS
e2e.components.DataSourcePicker.container().should('be.visible').click();
cy.contains(name).scrollIntoView().should('be.visible').click();
}
// Skipping due to flakiness/race conditions with same old arch test e2e/various-suite/prometheus-editor.spec.ts
describe.skip('Prometheus query editor', () => {
it('should have a kickstart component', () => {
navigateToEditor('Code', 'prometheus');
e2e.components.QueryBuilder.queryPatterns().scrollIntoView().should('exist');
});
it('should have an explain component', () => {
navigateToEditor('Code', 'prometheus');
e2e.components.DataSource.Prometheus.queryEditor.explain().scrollIntoView().should('exist');
});
it('should have an editor toggle component', () => {
navigateToEditor('Code', 'prometheus');
e2e.components.DataSource.Prometheus.queryEditor.editorToggle().scrollIntoView().should('exist');
});
it('should have an options component with legend, format, step, type and exemplars', () => {
navigateToEditor('Code', 'prometheus');
// open options
e2e.components.DataSource.Prometheus.queryEditor.options().scrollIntoView().should('exist').click();
// check options
e2e.components.DataSource.Prometheus.queryEditor.legend().scrollIntoView().should('exist');
e2e.components.DataSource.Prometheus.queryEditor.format().scrollIntoView().should('exist');
e2e.components.DataSource.Prometheus.queryEditor.step().scrollIntoView().should('exist');
e2e.components.DataSource.Prometheus.queryEditor.type().scrollIntoView().should('exist');
e2e.components.DataSource.Prometheus.queryEditor.exemplars().scrollIntoView().should('exist');
});
describe('Code editor', () => {
it('navigates to the code editor with editor type as code', () => {
navigateToEditor('Code', 'prometheusCode');
});
it('navigates to the code editor and opens the metrics browser with metric search, labels, label values, and all components', () => {
navigateToEditor('Code', 'prometheusCode');
getResources();
e2e.components.DataSource.Prometheus.queryEditor.code.queryField().should('exist');
e2e.components.DataSource.Prometheus.queryEditor.code.metricsBrowser
.openButton()
.contains('Metrics browser')
.click();
e2e.components.DataSource.Prometheus.queryEditor.code.metricsBrowser.selectMetric().should('exist');
e2e.components.DataSource.Prometheus.queryEditor.code.metricsBrowser.labelNamesFilter().should('exist');
e2e.components.DataSource.Prometheus.queryEditor.code.metricsBrowser.labelValuesFilter().should('exist');
e2e.components.DataSource.Prometheus.queryEditor.code.metricsBrowser.useQuery().should('exist');
e2e.components.DataSource.Prometheus.queryEditor.code.metricsBrowser.useAsRateQuery().should('exist');
e2e.components.DataSource.Prometheus.queryEditor.code.metricsBrowser.validateSelector().should('exist');
e2e.components.DataSource.Prometheus.queryEditor.code.metricsBrowser.clear().should('exist');
});
it('selects a metric in the metrics browser and uses the query', () => {
navigateToEditor('Code', 'prometheusCode');
getResources();
e2e.components.DataSource.Prometheus.queryEditor.code.metricsBrowser
.openButton()
.contains('Metrics browser')
.click();
e2e.components.DataSource.Prometheus.queryEditor.code.metricsBrowser.selectMetric().should('exist').type('met');
e2e.components.DataSource.Prometheus.queryEditor.code.metricsBrowser
.metricList()
.should('exist')
.contains('metric1')
.click();
e2e.components.DataSource.Prometheus.queryEditor.code.metricsBrowser.useQuery().should('exist').click();
e2e.components.DataSource.Prometheus.queryEditor.code.queryField().should('exist').contains('metric1');
});
});
describe('Query builder', () => {
it('navigates to the query builder with editor type as code', () => {
navigateToEditor('Builder', 'prometheusBuilder');
});
it('the query builder contains metric select, label filters and operations', () => {
navigateToEditor('Builder', 'prometheusBuilder');
getResources();
e2e.components.DataSource.Prometheus.queryEditor.builder.metricSelect().should('exist');
e2e.components.QueryBuilder.labelSelect().should('exist');
e2e.components.QueryBuilder.matchOperatorSelect().should('exist');
e2e.components.QueryBuilder.valueSelect().should('exist');
});
it('can select a metric and provide a hint', () => {
navigateToEditor('Builder', 'prometheusBuilder');
getResources();
e2e.components.DataSource.Prometheus.queryEditor.builder.metricSelect().should('exist').click();
selectOption('metric1');
e2e.components.DataSource.Prometheus.queryEditor.builder.hints().contains('hint: add rate');
});
it('should have the metrics explorer opened via the metric select', () => {
navigateToEditor('Builder', 'prometheusBuilder');
getResources();
e2e.components.DataSource.Prometheus.queryEditor.builder.metricSelect().should('exist').click();
selectOption('Metrics explorer');
e2e.components.DataSource.Prometheus.queryEditor.builder.metricsExplorer().should('exist');
});
});
});
function selectOption(option: string) {
e2e.components.Select.option().contains(option).should('be.visible').click();
}

View File

@ -1,123 +0,0 @@
import { e2e } from '../utils';
import { addDashboard } from '../utils/flows';
import { createPromDS, getResources } from './helpers/prometheus-helpers';
const DATASOURCE_ID = 'Prometheus';
const DATASOURCE_NAME = 'prometheusVariableDS';
/**
* Click dashboard settings and then the variables tab
*/
function navigateToVariables() {
e2e.components.NavToolbar.editDashboard.editButton().should('be.visible').click();
e2e.components.NavToolbar.editDashboard.settingsButton().should('be.visible').click();
e2e.components.Tab.title('Variables').click();
}
/**
* Begin the process of adding a query type variable for a Prometheus data source
*
* @param variableName the name of the variable as a label of the variable dropdown
*/
function addPrometheusQueryVariable(variableName: string) {
e2e.pages.Dashboard.Settings.Variables.List.addVariableCTAV2().click();
e2e.pages.Dashboard.Settings.Variables.Edit.General.generalNameInputV2().clear().type(variableName);
e2e.components.DataSourcePicker.container().should('be.visible').click();
cy.contains(DATASOURCE_NAME).scrollIntoView().should('be.visible').click();
getResources();
}
/**
* Create a Prometheus variable and navigate to the query editor to check that it is available to use.
*
* @param variableName name the variable
* @param queryType query type of 'Label names', 'Label values', 'Metrics', 'Query result', 'Series query' or 'Classic query'. These types should be imported from the Prometheus library eventually but not now because we are in the process of decoupling the DS from core grafana.
*/
function variableFlowToQueryEditor(variableName: string, queryType: string) {
addDashboard();
navigateToVariables();
addPrometheusQueryVariable(variableName);
// select query type
e2e.components.DataSource.Prometheus.variableQueryEditor.queryType().click();
selectOption(queryType);
// apply the variable
e2e.pages.Dashboard.Settings.Variables.Edit.General.applyButton().click();
// close to return to dashboard
e2e.components.NavToolbar.editDashboard.backToDashboardButton().should('be.visible').click();
// add visualization
e2e.pages.AddDashboard.itemButton('Create new panel button').should('be.visible').click();
// close the data source picker modal
cy.get('[aria-label="Close"]').click();
// select prom data source from the data source list with the useful data-testid
e2e.components.DataSourcePicker.inputV2().click({ force: true }).type(`${DATASOURCE_NAME}{enter}`);
// confirm the variable exists in the correct input
// use the variable query type from the library in the future
switch (queryType) {
case 'Label names':
e2e.components.QueryBuilder.labelSelect().should('exist').click({ force: true });
selectOption(`${variableName}`);
case 'Label values':
e2e.components.QueryBuilder.valueSelect().should('exist').click({ force: true });
selectOption(`${variableName}`);
case 'Metrics':
e2e.components.DataSource.Prometheus.queryEditor.builder.metricSelect().should('exist').click({ force: true });
selectOption(`${variableName}`);
default:
// do nothing
}
}
// Skipping due to race conditions with same old arch test e2e/various-suite/prometheus-variable-editor.spec.ts
describe.skip('Prometheus variable query editor', () => {
beforeEach(() => {
createPromDS(DATASOURCE_ID, DATASOURCE_NAME);
});
it('should navigate to variable query editor', () => {
addDashboard();
navigateToVariables();
});
it('should select a query type for a Prometheus variable query', () => {
addDashboard();
navigateToVariables();
addPrometheusQueryVariable('labelsVariable');
// select query type
e2e.components.DataSource.Prometheus.variableQueryEditor.queryType().click();
selectOption('Label names');
});
it('should create a label names variable that is selectable in the label select in query builder', () => {
addDashboard();
navigateToVariables();
variableFlowToQueryEditor('labelnames', 'Label names');
});
it('should create a label values variable that is selectable in the label values select in query builder', () => {
addDashboard();
navigateToVariables();
variableFlowToQueryEditor('labelvalues', 'Label values');
});
it('should create a metric names variable that is selectable in the metric select in query builder', () => {
addDashboard();
navigateToVariables();
variableFlowToQueryEditor('metrics', 'Metrics');
});
});
function selectOption(option: string) {
e2e.components.Select.option().contains(option).should('be.visible').click();
}

View File

@ -1,47 +0,0 @@
import { e2e } from '../utils';
describe('Trace view', () => {
beforeEach(() => {
e2e.flows.login(Cypress.env('USERNAME'), Cypress.env('PASSWORD'));
});
it('Can lazy load big traces', () => {
cy.intercept('POST', '**/api/ds/query*', (req) => {
if (!req.url.includes('ds_type=jaeger')) {
return;
}
req.reply({ fixture: 'long-trace-response-backend.json' });
}).as('longTrace');
e2e.pages.Explore.visit();
e2e.components.DataSourcePicker.container().should('be.visible').type('gdev-jaeger{enter}');
// Wait for the query editor to be set correctly
e2e.components.QueryEditorRows.rows().within(() => {
cy.contains('gdev-jaeger').should('be.visible');
});
// type this with 0 delay to prevent flaky tests due to cursor position changing between rerenders
e2e.components.QueryField.container().should('be.visible').type('trace', {
delay: 0,
});
// Use shift+enter to execute the query as it's more stable than clicking the execute button
e2e.components.QueryField.container().type('{shift+enter}');
cy.wait('@longTrace');
e2e.components.TraceViewer.spanBar().should('be.visible');
e2e.components.TraceViewer.spanBar()
.its('length')
.then((oldLength) => {
e2e.pages.Explore.General.scrollView().children().first().scrollTo('center');
// After scrolling we should load more spans
e2e.components.TraceViewer.spanBar().should(($span) => {
expect($span.length).to.be.gt(oldLength);
});
});
});
});