E2E: Run playwright cloud plugins tests as part of github actions (#109055)

* add github workflow scaffolding

* update comments

* Add image and resource commands

* Add secrets paths

* Block workflow run for forks

* ignore via package.json, update CODEOWNERS

* fix workflow path

* remove old azure monitor test

* pull docker image first

* add permissions for docker pull step

* checkout first

* keep creds file

* try all in one job

* with creds...

* add cloud: 'azure'

* pass CLOUD to docker

* add -playwright

* actually use the env vars

* don't need to pass CLOUD env var

* remove commented out code and tidy up

* kick CI

* Update container names and set PLAYWRIGHT_CI

* Update path

* fix zizmor violation

* use bigger runner, add double quoting

* add separate command and increase timeout

* remove timeout

* parameterise the e2e command in CI

* move cloud-plugins-e2e-tests into normal e2e test workflow

* fix detect changes

* pass creds into dagger

* try remove quotes

* add a debug log

* exec playwright command after mounting file

* reassign e2eContainer, add change to check the tests fail correctly

* fix test

---------

Co-authored-by: Andreas Christou <andreas.christou@grafana.com>
This commit is contained in:
Ashley Harrison
2025-08-19 09:25:16 +01:00
committed by GitHub
parent 1e6719f571
commit 0687017595
8 changed files with 125 additions and 376 deletions

2
.github/CODEOWNERS vendored
View File

@ -412,8 +412,8 @@
/public/locales/i18next-parser-enterprise.config.cjs @grafana/grafana-frontend-platform /public/locales/i18next-parser-enterprise.config.cjs @grafana/grafana-frontend-platform
/public/app/core/internationalization/ @grafana/grafana-frontend-platform /public/app/core/internationalization/ @grafana/grafana-frontend-platform
/e2e/ @grafana/grafana-frontend-platform /e2e/ @grafana/grafana-frontend-platform
/e2e/cloud-plugins-suite/ @grafana/partner-datasources
/e2e-playwright/ @grafana/grafana-frontend-platform /e2e-playwright/ @grafana/grafana-frontend-platform
/e2e-playwright/cloud-plugins-suite/ @grafana/partner-datasources
/e2e-playwright/dashboard-new-layouts @grafana/dashboards-squad /e2e-playwright/dashboard-new-layouts @grafana/dashboards-squad
/e2e-playwright/dashboards-suite/dashboard-browse-nested.spec.ts @grafana/grafana-search-navigate-organise /e2e-playwright/dashboards-suite/dashboard-browse-nested.spec.ts @grafana/grafana-search-navigate-organise
/e2e-playwright/dashboards-suite/dashboard-browse.spec.ts @grafana/grafana-search-navigate-organise /e2e-playwright/dashboards-suite/dashboard-browse.spec.ts @grafana/grafana-search-navigate-organise

View File

@ -19,6 +19,9 @@ outputs:
value: ${{ steps.changed-files.outputs.e2e_any_changed == 'true' || value: ${{ steps.changed-files.outputs.e2e_any_changed == 'true' ||
steps.changed-files.outputs.backend_any_changed == 'true' || steps.changed-files.outputs.backend_any_changed == 'true' ||
steps.changed-files.outputs.frontend_any_changed == 'true' || 'true' }} steps.changed-files.outputs.frontend_any_changed == 'true' || 'true' }}
e2e-cloud-plugins:
description: Whether the cloud plugins code or tests have changed in any way
value: ${{ steps.changed-files.outputs.e2e_cloud_plugins_any_changed || 'true' }}
dev-tooling: dev-tooling:
description: Whether the dev tooling or self have changed in any way description: Whether the dev tooling or self have changed in any way
value: ${{ steps.changed-files.outputs.dev_tooling_any_changed || 'true' }} value: ${{ steps.changed-files.outputs.dev_tooling_any_changed || 'true' }}
@ -102,6 +105,11 @@ runs:
- 'conf/**' - 'conf/**'
- 'cypress.config.js' - 'cypress.config.js'
- '${{ inputs.self }}' - '${{ inputs.self }}'
e2e_cloud_plugins:
- 'pkg/tsdb/azuremonitor/**'
- 'public/app/plugins/datasource/azuremonitor/**'
- 'e2e-playwright/cloud-plugins-suite/azure-monitor.spec.ts'
- '${{ inputs.self }}'
dev_tooling: dev_tooling:
- '.github/actions/setup-enterprise/**' - '.github/actions/setup-enterprise/**'
- '.github/actions/checkout/**' - '.github/actions/checkout/**'
@ -139,6 +147,8 @@ runs:
echo " --> ${{ steps.changed-files.outputs.e2e_all_changed_files }}" echo " --> ${{ steps.changed-files.outputs.e2e_all_changed_files }}"
echo " --> ${{ steps.changed-files.outputs.backend_all_changed_files }}" echo " --> ${{ steps.changed-files.outputs.backend_all_changed_files }}"
echo " --> ${{ steps.changed-files.outputs.frontend_all_changed_files }}" echo " --> ${{ steps.changed-files.outputs.frontend_all_changed_files }}"
echo "E2E cloud plugins: ${{ steps.changed-files.outputs.e2e_cloud_plugins_any_changed || 'true' }}"
echo " --> ${{ steps.changed-files.outputs.e2e_cloud_plugins_all_changed_files }}"
echo "Dev Tooling: ${{ steps.changed-files.outputs.dev_tooling_any_changed || 'true' }}" echo "Dev Tooling: ${{ steps.changed-files.outputs.dev_tooling_any_changed || 'true' }}"
echo " --> ${{ steps.changed-files.outputs.dev_tooling_all_changed_files }}" echo " --> ${{ steps.changed-files.outputs.dev_tooling_all_changed_files }}"
echo "Docs: ${{ steps.changed-files.outputs.docs_any_changed || 'true' }}" echo "Docs: ${{ steps.changed-files.outputs.docs_any_changed || 'true' }}"

View File

@ -26,6 +26,7 @@ jobs:
contents: read contents: read
outputs: outputs:
changed: ${{ steps.detect-changes.outputs.e2e }} changed: ${{ steps.detect-changes.outputs.e2e }}
cloud_plugins_changed: ${{ steps.detect-changes.outputs.e2e-cloud-plugins }}
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
with: with:
@ -318,9 +319,76 @@ jobs:
path: ./blob-report path: ./blob-report
retention-days: 1 retention-days: 1
run-azure-monitor-e2e:
if: needs.detect-changes.outputs.cloud_plugins_changed == 'true' && github.event.pull_request.head.repo.fork == false
runs-on: ubuntu-x64-large
needs:
- build-grafana
- detect-changes
permissions:
contents: read
id-token: write
steps:
- uses: actions/checkout@v4
with:
persist-credentials: false
- uses: grafana/shared-workflows/actions/login-to-gar@login-to-gar-v0.4.0
id: login-to-gar
with:
registry: "us-docker.pkg.dev"
environment: "dev"
- id: pull-docker-image
run: |
docker pull us-docker.pkg.dev/grafanalabs-dev/docker-oss-plugin-partnerships-dev/e2e-playwright:latest
- id: vault-secrets
uses: grafana/shared-workflows/actions/get-vault-secrets@main
with:
repo_secrets: |
AZURE_SP_APP_ID=cpp-azure-resourcemanager-credentials:application_id
AZURE_SP_PASSWORD=cpp-azure-resourcemanager-credentials:application_secret
AZURE_TENANT=cpp-azure-resourcemanager-credentials:tenant_id
- id: deploy-resources
env:
AZURE_SP_APP_ID: ${{ env.AZURE_SP_APP_ID}}
AZURE_SP_PASSWORD: ${{ env.AZURE_SP_PASSWORD}}
AZURE_TENANT: ${{ env.AZURE_TENANT }}
NAME: ${{ github.ref_name }}
run: |
docker container run --name cpp-e2e-deploy -e AZURE_SP_APP_ID -e AZURE_SP_PASSWORD -e AZURE_TENANT -e PLAYWRIGHT_CI=true us-docker.pkg.dev/grafanalabs-dev/docker-oss-plugin-partnerships-dev/e2e-playwright:latest ./cpp-e2e/scripts/ci-run-playwright.sh azure "${NAME}" deploy
- id: extract-creds
# see https://github.com/grafana/oss-plugin-partnerships/blob/a77040d0456003cd258668b61d542dc7c75db5b5/e2e/scripts/deploy.sh#L25 for path
run: |
docker cp cpp-e2e-deploy:/outputs.json /tmp/outputs.json
- uses: actions/download-artifact@v4
with:
name: grafana-tar-gz
- name: Run E2E tests
uses: dagger/dagger-for-github@e47aba410ef9bb9ed81a4d2a97df31061e5e842e
with:
verb: run
args: go run ./pkg/build/e2e-playwright --package=grafana.tar.gz --playwright-command="yarn e2e:playwright:cloud-plugins" --cloud-plugin-creds=/tmp/outputs.json
- name: Destroy resources
if: always() && steps.deploy-resources.outcome == 'success'
env:
AZURE_SP_APP_ID: ${{ env.AZURE_SP_APP_ID }}
AZURE_SP_PASSWORD: ${{ env.AZURE_SP_PASSWORD }}
AZURE_TENANT: ${{ env.AZURE_TENANT }}
NAME: ${{ github.ref_name }}
run: |
docker container run --name cpp-e2e-destroy -e AZURE_SP_APP_ID -e AZURE_SP_PASSWORD -e AZURE_TENANT us-docker.pkg.dev/grafanalabs-dev/docker-oss-plugin-partnerships-dev/e2e-playwright:latest ./cpp-e2e/scripts/ci-run-playwright.sh azure "${NAME}" destroy
required-playwright-tests: required-playwright-tests:
needs: needs:
- run-playwright-tests - run-playwright-tests
- run-azure-monitor-e2e
- run-storybook-test - run-storybook-test
- build-grafana - build-grafana
if: ${{ !cancelled() }} if: ${{ !cancelled() }}

View File

@ -4,12 +4,12 @@ import { Page } from 'playwright-core';
import { v4 as uuidv4 } from 'uuid'; import { v4 as uuidv4 } from 'uuid';
import { import {
test,
expect,
CreateDataSourcePageArgs, CreateDataSourcePageArgs,
DashboardPage,
DataSourceConfigPage, DataSourceConfigPage,
E2ESelectorGroups, E2ESelectorGroups,
DashboardPage, expect,
test,
} from '@grafana/plugin-e2e'; } from '@grafana/plugin-e2e';
import { AzureQueryType } from '../../public/app/plugins/datasource/azuremonitor/dataquery.gen'; import { AzureQueryType } from '../../public/app/plugins/datasource/azuremonitor/dataquery.gen';
@ -71,8 +71,7 @@ async function provisionAzureMonitorDatasources(
await configPage.saveAndTest(); await configPage.saveAndTest();
} }
// TODO unskip when we've figured out how to populate the credentials in CI test.describe(
test.describe.skip(
'Azure Monitor datasource', 'Azure Monitor datasource',
{ {
tag: ['@cloud-plugins'], tag: ['@cloud-plugins'],
@ -84,7 +83,7 @@ test.describe.skip(
// Check if we're running in CI // Check if we're running in CI
const CI = process.env.CI; const CI = process.env.CI;
if (CI) { if (CI) {
const outputs = JSON.parse(readFileSync('outputs.json', 'utf8')); const outputs = JSON.parse(readFileSync('/tmp/outputs.json', 'utf8'));
datasourceConfig = { datasourceConfig = {
jsonData: { jsonData: {
cloudName: 'Azure', cloudName: 'Azure',
@ -134,6 +133,7 @@ test.describe.skip(
await expect(page.getByText(rootSubscription)).toBeVisible({ timeout: 30000 }); await expect(page.getByText(rootSubscription)).toBeVisible({ timeout: 30000 });
const resourceSearchInput = page.getByTestId(azMonSelectors.components.queryEditor.resourcePicker.search.input); const resourceSearchInput = page.getByTestId(azMonSelectors.components.queryEditor.resourcePicker.search.input);
await resourceSearchInput.fill(storageAcctName); await resourceSearchInput.fill(storageAcctName);
await resourceSearchInput.press('Enter');
await expect(page.getByText(storageAcctName)).toBeVisible({ timeout: 30000 }); await expect(page.getByText(storageAcctName)).toBeVisible({ timeout: 30000 });
await page.getByText(storageAcctName).click(); await page.getByText(storageAcctName).click();
const applyButton = page.getByTestId(azMonSelectors.components.queryEditor.resourcePicker.apply.button); const applyButton = page.getByTestId(azMonSelectors.components.queryEditor.resourcePicker.apply.button);
@ -164,6 +164,7 @@ test.describe.skip(
await resourcePickerButton.click(); await resourcePickerButton.click();
await expect(page.getByText(rootSubscription)).toBeVisible({ timeout: 30000 }); await expect(page.getByText(rootSubscription)).toBeVisible({ timeout: 30000 });
await resourceSearchInput.fill(logAnalyticsName); await resourceSearchInput.fill(logAnalyticsName);
await resourceSearchInput.press('Enter');
await expect(page.getByText(logAnalyticsName)).toBeVisible({ timeout: 30000 }); await expect(page.getByText(logAnalyticsName)).toBeVisible({ timeout: 30000 });
await page.getByText(logAnalyticsName).click(); await page.getByText(logAnalyticsName).click();
await applyButton.click(); await applyButton.click();
@ -220,6 +221,7 @@ test.describe.skip(
await resourcePickerButton.click(); await resourcePickerButton.click();
await expect(page.getByText(rootSubscription)).toBeVisible({ timeout: 30000 }); await expect(page.getByText(rootSubscription)).toBeVisible({ timeout: 30000 });
await resourceSearchInput.fill(applicationInsightsName); await resourceSearchInput.fill(applicationInsightsName);
await resourceSearchInput.press('Enter');
await expect(page.getByText(applicationInsightsName)).toBeVisible({ timeout: 30000 }); await expect(page.getByText(applicationInsightsName)).toBeVisible({ timeout: 30000 });
await page.getByText(applicationInsightsName).click(); await page.getByText(applicationInsightsName).click();
await applyButton.click(); await applyButton.click();

View File

@ -1,360 +0,0 @@
import { Interception } from 'cypress/types/net-stubbing';
import { load } from 'js-yaml';
import { v4 as uuidv4 } from 'uuid';
import { selectors as rawSelectors } from '@grafana/e2e-selectors';
import { selectors } from '../../public/app/plugins/datasource/azuremonitor/e2e/selectors';
import { AzureQueryType } from '../../public/app/plugins/datasource/azuremonitor/types/query';
import {
AzureMonitorDataSourceJsonData,
AzureMonitorDataSourceSecureJsonData,
} from '../../public/app/plugins/datasource/azuremonitor/types/types';
import { e2e } from '../utils';
const provisioningPath = `provisioning/datasources/azmonitor-ds.yaml`;
const e2eSelectors = e2e.getSelectors(selectors.components);
type AzureMonitorConfig = {
secureJsonData: AzureMonitorDataSourceSecureJsonData;
jsonData: AzureMonitorDataSourceJsonData;
};
type AzureMonitorProvision = { datasources: AzureMonitorConfig[] };
const dataSourceName = `Azure Monitor E2E Tests - ${uuidv4()}`;
const maxRetryCount = 3;
Cypress.Commands.add('checkHealthRetryable', function (fn: Function, retryCount: number) {
cy.then(() => {
const result = fn(++retryCount);
result.then((res: Interception) => {
if (retryCount < maxRetryCount && res.response.statusCode !== 200) {
cy.wait(20000);
cy.checkHealthRetryable(fn, retryCount);
}
});
});
});
function provisionAzureMonitorDatasources(datasources: AzureMonitorProvision[]) {
const datasource = datasources[0].datasources[0];
cy.intercept(/subscriptions/).as('subscriptions');
e2e.flows.addDataSource({
type: 'Azure Monitor',
name: dataSourceName,
form: () => {
e2eSelectors.configEditor.azureCloud.input().find('input').type('Azure').type('{enter}');
// We set the log value to false here to ensure that secrets aren't printed to logs
e2eSelectors.configEditor.tenantID.input().find('input').type(datasource.jsonData.tenantId, { log: false });
e2eSelectors.configEditor.clientID.input().find('input').type(datasource.jsonData.clientId, { log: false });
e2eSelectors.configEditor.clientSecret
.input()
.find('input')
.type(datasource.secureJsonData.clientSecret, { log: false });
e2eSelectors.configEditor.loadSubscriptions.button().click().wait('@subscriptions').wait(500);
e2eSelectors.configEditor.defaultSubscription.input().find('input').type('datasources{enter}');
// We can do this because awaitHealth is set to true so @health is defined
cy.checkHealthRetryable(() => {
return e2e.pages.DataSource.saveAndTest().click().wait('@health');
}, 0);
},
expectedAlertMessage: 'Successfully connected to all Azure Monitor endpoints',
// Reduce the timeout from 30s to error faster when an invalid alert message is presented
timeout: 10000,
awaitHealth: true,
});
}
// Helper function to add template variables
const addAzureMonitorVariable = (
name: string,
type: AzureQueryType,
isFirst: boolean,
options?: { subscription?: string; resourceGroup?: string; namespace?: string; resource?: string; region?: string }
) => {
e2e.components.NavToolbar.editDashboard.editButton().should('be.visible').click();
e2e.components.NavToolbar.editDashboard.settingsButton().should('be.visible').click();
e2e.components.Tab.title('Variables').click();
if (isFirst) {
e2e.pages.Dashboard.Settings.Variables.List.addVariableCTAV2().click();
} else {
cy.get(`[data-testid="${rawSelectors.pages.Dashboard.Settings.Variables.List.newButton}"]`).click();
}
e2e.pages.Dashboard.Settings.Variables.Edit.General.generalNameInputV2().clear().type(name);
e2e.components.DataSourcePicker.inputV2().type(`${dataSourceName}{enter}`);
e2eSelectors.variableEditor.queryType
.input()
.find('input')
.type(`${type.replace('Azure', '').trim()}{enter}`);
switch (type) {
case AzureQueryType.ResourceGroupsQuery:
e2eSelectors.variableEditor.subscription.input().find('input').type(`${options?.subscription}{enter}`);
break;
case AzureQueryType.LocationsQuery:
e2eSelectors.variableEditor.subscription.input().find('input').type(`${options?.subscription}{enter}`);
break;
case AzureQueryType.NamespacesQuery:
e2eSelectors.variableEditor.subscription.input().find('input').type(`${options?.subscription}{enter}`);
e2eSelectors.variableEditor.resourceGroup.input().find('input').type(`${options?.resourceGroup}{enter}`);
break;
case AzureQueryType.ResourceNamesQuery:
e2eSelectors.variableEditor.subscription.input().find('input').type(`${options?.subscription}{enter}`);
e2eSelectors.variableEditor.resourceGroup.input().find('input').type(`${options?.resourceGroup}{enter}`);
e2eSelectors.variableEditor.namespace.input().find('input').type(`${options?.namespace}{enter}`);
e2eSelectors.variableEditor.region.input().find('input').type(`${options?.region}{enter}`);
break;
case AzureQueryType.MetricNamesQuery:
e2eSelectors.variableEditor.subscription.input().find('input').type(`${options?.subscription}{enter}`);
e2eSelectors.variableEditor.resourceGroup.input().find('input').type(`${options?.resourceGroup}{enter}`);
e2eSelectors.variableEditor.namespace.input().find('input').type(`${options?.namespace}{enter}`);
e2eSelectors.variableEditor.resource.input().find('input').type(`${options?.resource}{enter}`);
break;
}
e2e.pages.Dashboard.Settings.Variables.Edit.General.submitButton().click();
e2e.components.NavToolbar.editDashboard.backToDashboardButton().click();
e2e.components.NavToolbar.editDashboard.exitButton().click();
};
const storageAcctName = 'azmonteststorage';
const logAnalyticsName = 'az-mon-test-logs';
const applicationInsightsName = 'az-mon-test-ai-a';
describe('Azure monitor datasource', () => {
before(() => {
e2e.flows.login(Cypress.env('USERNAME'), Cypress.env('PASSWORD'));
// Add datasource
// This variable will be set in CI
const CI = Cypress.env('CI');
if (CI) {
cy.readFile('outputs.json').then((outputs) => {
provisionAzureMonitorDatasources([
{
datasources: [
{
jsonData: {
cloudName: 'Azure',
tenantId: outputs.tenantId,
clientId: outputs.clientId,
},
secureJsonData: { clientSecret: outputs.clientSecret },
},
],
},
]);
});
} else {
cy.readFile(provisioningPath).then((azMonitorProvision: string) => {
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
const yaml = load(azMonitorProvision) as AzureMonitorProvision;
provisionAzureMonitorDatasources([yaml]);
});
}
e2e.setScenarioContext({ addedDataSources: [] });
});
beforeEach(() => {
e2e.flows.login(Cypress.env('USERNAME'), Cypress.env('PASSWORD'));
});
after(() => {
e2e.flows.login(Cypress.env('USERNAME'), Cypress.env('PASSWORD'));
e2e.flows.revertAllChanges();
});
it('create dashboard, add panel for metrics, log analytics, ARG, and traces queries', () => {
e2e.flows.addDashboard({
timeRange: {
from: 'now-6h',
to: 'now',
zone: 'Coordinated Universal Time',
},
});
e2e.flows.addPanel({
dataSourceName,
visitDashboardAtStart: false,
queriesForm: () => {
e2eSelectors.queryEditor.resourcePicker.select.button().click();
e2eSelectors.queryEditor.resourcePicker.search
.input()
.wait(100)
.type(storageAcctName)
.wait(500)
.type('{enter}');
cy.contains(storageAcctName).click();
e2eSelectors.queryEditor.resourcePicker.apply.button().click();
cy.contains('microsoft.storage/storageaccounts');
e2eSelectors.queryEditor.metricsQueryEditor.metricName.input().find('input').type('Used capacity{enter}');
},
timeout: 10000,
});
e2e.components.NavToolbar.editDashboard.backToDashboardButton().click();
e2e.components.NavToolbar.editDashboard.exitButton().click();
e2e.flows.addPanel({
dataSourceName,
visitDashboardAtStart: false,
queriesForm: () => {
e2eSelectors.queryEditor.header.select().find('input').type('Logs{enter}');
e2eSelectors.queryEditor.resourcePicker.select.button().click();
e2eSelectors.queryEditor.resourcePicker.search
.input()
.wait(100)
.type(logAnalyticsName)
.wait(500)
.type('{enter}');
cy.contains(logAnalyticsName).click();
e2eSelectors.queryEditor.resourcePicker.apply.button().click();
e2e.components.CodeEditor.container().type('AzureDiagnostics');
e2eSelectors.queryEditor.logsQueryEditor.formatSelection.input().type('Time series{enter}');
},
timeout: 10000,
});
e2e.components.NavToolbar.editDashboard.backToDashboardButton().click();
e2e.components.NavToolbar.editDashboard.exitButton().click();
e2e.flows.addPanel({
dataSourceName,
visitDashboardAtStart: false,
queriesForm: () => {
e2eSelectors.queryEditor.header.select().find('input').type('Azure Resource Graph{enter}');
cy.wait(2000); // Need to wait for code editor to completely load
e2eSelectors.queryEditor.argsQueryEditor.subscriptions.input().find('[aria-label="Clear value"]').click();
e2eSelectors.queryEditor.argsQueryEditor.subscriptions.input().find('input').type('datasources{enter}');
e2e.components.CodeEditor.container().type(
"Resources | where resourceGroup == 'cloud-plugins-e2e-test-azmon' | project name, resourceGroup"
);
e2e.components.PanelEditor.toggleTableView().click({ force: true });
},
timeout: 10000,
});
e2e.components.NavToolbar.editDashboard.backToDashboardButton().click();
e2e.components.NavToolbar.editDashboard.exitButton().click();
e2e.flows.addPanel({
dataSourceName,
visitDashboardAtStart: false,
queriesForm: () => {
e2eSelectors.queryEditor.header.select().find('input').type('Traces{enter}');
e2eSelectors.queryEditor.resourcePicker.select.button().click();
e2eSelectors.queryEditor.resourcePicker.search
.input()
.wait(100)
.type(applicationInsightsName)
.wait(500)
.type('{enter}');
cy.contains(applicationInsightsName).click();
e2eSelectors.queryEditor.resourcePicker.apply.button().click();
cy.wait(10000);
e2eSelectors.queryEditor.logsQueryEditor.formatSelection.input().type('Trace{enter}');
},
timeout: 10000,
});
});
it('creates a dashboard that includes a template variable', () => {
e2e.flows.addDashboard({
timeRange: {
from: 'now-6h',
to: 'now',
zone: 'Coordinated Universal Time',
},
});
addAzureMonitorVariable('subscription', AzureQueryType.SubscriptionsQuery, true);
addAzureMonitorVariable('resourceGroups', AzureQueryType.ResourceGroupsQuery, false, {
subscription: '$subscription',
});
addAzureMonitorVariable('namespaces', AzureQueryType.NamespacesQuery, false, {
subscription: '$subscription',
resourceGroup: '$resourceGroups',
});
addAzureMonitorVariable('region', AzureQueryType.LocationsQuery, false, {
subscription: '$subscription',
});
addAzureMonitorVariable('resource', AzureQueryType.ResourceNamesQuery, false, {
subscription: '$subscription',
resourceGroup: '$resourceGroups',
namespace: '$namespace',
region: '$region',
});
e2e.pages.Dashboard.SubMenu.submenuItemLabels('subscription')
.parent()
.within(() => {
cy.get('input').click();
});
e2e.components.Select.option().contains('grafanalabs-datasources-dev').click();
e2e.pages.Dashboard.SubMenu.submenuItemLabels('resourceGroups')
.parent()
.within(() => {
cy.get('input').type('cloud-plugins-e2e-test-azmon{downArrow}{enter}');
});
e2e.pages.Dashboard.SubMenu.submenuItemLabels('namespaces')
.parent()
.within(() => {
cy.get('input').type('microsoft.storage/storageaccounts{downArrow}{enter}');
});
e2e.pages.Dashboard.SubMenu.submenuItemLabels('region')
.parent()
.within(() => {
cy.get('input').type('uk south{downArrow}{enter}');
});
e2e.pages.Dashboard.SubMenu.submenuItemLabels('resource')
.parent()
.within(() => {
cy.get('input').type(`${storageAcctName}{downArrow}{enter}`);
});
e2e.flows.addPanel({
dataSourceName,
visitDashboardAtStart: false,
queriesForm: () => {
e2eSelectors.queryEditor.resourcePicker.select.button().click();
e2eSelectors.queryEditor.resourcePicker.advanced.collapse().click();
e2eSelectors.queryEditor.resourcePicker.advanced.subscription.input().find('input').type('$subscription');
e2eSelectors.queryEditor.resourcePicker.advanced.resourceGroup.input().find('input').type('$resourceGroups');
e2eSelectors.queryEditor.resourcePicker.advanced.namespace.input().find('input').type('$namespaces');
e2eSelectors.queryEditor.resourcePicker.advanced.region.input().find('input').type('$region');
e2eSelectors.queryEditor.resourcePicker.advanced.resource.input().find('input').type('$resource');
e2eSelectors.queryEditor.resourcePicker.apply.button().click();
e2eSelectors.queryEditor.metricsQueryEditor.metricName.input().find('input').type('Transactions{enter}');
},
timeout: 10000,
});
});
it.skip('creates a dashboard that includes an annotation', () => {
e2e.flows.addDashboard({
timeRange: {
from: '2022-10-03 00:00:00',
to: '2022-10-03 23:59:59',
zone: 'Coordinated Universal Time',
},
});
e2e.components.PageToolbar.item('Dashboard settings').click();
e2e.components.Tab.title('Annotations').click();
e2e.pages.Dashboard.Settings.Annotations.List.addAnnotationCTAV2().click();
e2e.pages.Dashboard.Settings.Annotations.Settings.name().type('TestAnnotation');
e2e.components.DataSourcePicker.inputV2().click().type(`${dataSourceName}{enter}`);
e2eSelectors.queryEditor.resourcePicker.select.button().click();
e2eSelectors.queryEditor.resourcePicker.search.input().type(storageAcctName);
cy.contains(storageAcctName).click();
e2eSelectors.queryEditor.resourcePicker.apply.button().click();
cy.contains('microsoft.storage/storageaccounts');
e2eSelectors.queryEditor.metricsQueryEditor.metricName.input().find('input').type('Transactions{enter}');
cy.get('table').contains('text').parent().find('input').click().type('Transactions (number){enter}');
e2e.components.PageToolbar.item('Go Back').click();
e2e.flows.addPanel({
dataSourceName,
visitDashboardAtStart: false,
queriesForm: () => {
e2eSelectors.queryEditor.resourcePicker.select.button().click();
e2eSelectors.queryEditor.resourcePicker.search.input().type(storageAcctName);
cy.contains(storageAcctName).click();
e2eSelectors.queryEditor.resourcePicker.apply.button().click();
cy.contains('microsoft.storage/storageaccounts');
e2eSelectors.queryEditor.metricsQueryEditor.metricName.input().find('input').type('Used capacity{enter}');
},
});
});
});

View File

@ -21,7 +21,8 @@
"e2e:enterprise": "./e2e/start-and-run-suite enterprise", "e2e:enterprise": "./e2e/start-and-run-suite enterprise",
"e2e:enterprise:dev": "./e2e/start-and-run-suite enterprise dev", "e2e:enterprise:dev": "./e2e/start-and-run-suite enterprise dev",
"e2e:enterprise:debug": "./e2e/start-and-run-suite enterprise debug", "e2e:enterprise:debug": "./e2e/start-and-run-suite enterprise debug",
"e2e:playwright": "yarn playwright test", "e2e:playwright": "yarn playwright test --grep-invert @cloud-plugins",
"e2e:playwright:cloud-plugins": "yarn playwright test --grep @cloud-plugins",
"e2e:playwright:storybook": "yarn playwright test -c playwright.storybook.config.ts", "e2e:playwright:storybook": "yarn playwright test -c playwright.storybook.config.ts",
"e2e:acceptance": "yarn playwright test --grep @acceptance", "e2e:acceptance": "yarn playwright test --grep @acceptance",
"e2e:storybook": "PORT=9001 ./e2e/run-suite storybook true", "e2e:storybook": "PORT=9001 ./e2e/run-suite storybook true",

View File

@ -23,6 +23,8 @@ type RunTestOpts struct {
HTMLReportExportDir string HTMLReportExportDir string
BlobReportExportDir string BlobReportExportDir string
TestResultsExportDir string TestResultsExportDir string
PlaywrightCommand string
CloudPluginCreds *dagger.File
} }
func RunTest( func RunTest(
@ -47,10 +49,16 @@ func RunTest(
WithEnvVariable("bustcache", "1"). WithEnvVariable("bustcache", "1").
WithEnvVariable("PLAYWRIGHT_HTML_OPEN", "never"). WithEnvVariable("PLAYWRIGHT_HTML_OPEN", "never").
WithEnvVariable("PLAYWRIGHT_HTML_OUTPUT_DIR", htmlResultsDir). WithEnvVariable("PLAYWRIGHT_HTML_OUTPUT_DIR", htmlResultsDir).
WithEnvVariable("PLAYWRIGHT_BLOB_OUTPUT_DIR", blobResultsDir). WithEnvVariable("PLAYWRIGHT_BLOB_OUTPUT_DIR", blobResultsDir)
WithExec(playwrightCommand, dagger.ContainerWithExecOpts{
Expect: dagger.ReturnTypeAny, if opts.CloudPluginCreds != nil {
}) fmt.Println("DEBUG: CloudPluginCreds file is provided, mounting to /tmp/outputs.json")
e2eContainer = e2eContainer.WithMountedFile("/tmp/outputs.json", opts.CloudPluginCreds)
}
e2eContainer = e2eContainer.WithExec(playwrightCommand, dagger.ContainerWithExecOpts{
Expect: dagger.ReturnTypeAny,
})
if opts.TestResultsExportDir != "" { if opts.TestResultsExportDir != "" {
_, err := e2eContainer.Directory(testResultsDir).Export(ctx, opts.TestResultsExportDir) _, err := e2eContainer.Directory(testResultsDir).Export(ctx, opts.TestResultsExportDir)
@ -89,14 +97,14 @@ func buildPlaywrightCommand(opts RunTestOpts) []string {
playwrightReporters = append(playwrightReporters, "blob") playwrightReporters = append(playwrightReporters, "blob")
} }
playwrightCommand := []string{ playwrightExec := strings.Split(opts.PlaywrightCommand, " ")
"yarn",
"e2e:playwright", playwrightCommand := append(playwrightExec,
"--reporter", "--reporter",
strings.Join(playwrightReporters, ","), strings.Join(playwrightReporters, ","),
"--output", "--output",
testResultsDir, testResultsDir,
} )
if opts.Shard != "" { if opts.Shard != "" {
playwrightCommand = append(playwrightCommand, "--shard", opts.Shard) playwrightCommand = append(playwrightCommand, "--shard", opts.Shard)

View File

@ -71,6 +71,17 @@ func NewApp() *cli.Command {
Usage: "Enables the blob reporter, exported to this directory. Useful with --shard (optional)", Usage: "Enables the blob reporter, exported to this directory. Useful with --shard (optional)",
Validator: mustBeDir("blob-dir", true, true), Validator: mustBeDir("blob-dir", true, true),
}, },
&cli.StringFlag{
Name: "playwright-command",
Usage: "The playwright command to run.",
Value: "yarn e2e:playwright",
},
&cli.StringFlag{
Name: "cloud-plugin-creds",
Usage: "Path to the cloud plugin credentials file (only required for running @cloud-plugins e2e tests)",
Validator: mustBeFile("cloud-plugin-creds", true),
TakesFile: true,
},
}, },
Action: run, Action: run,
} }
@ -80,10 +91,12 @@ func run(ctx context.Context, cmd *cli.Command) error {
grafanaDir := cmd.String("grafana-dir") grafanaDir := cmd.String("grafana-dir")
targzPath := cmd.String("package") targzPath := cmd.String("package")
licensePath := cmd.String("license") licensePath := cmd.String("license")
cloudPluginCredsPath := cmd.String("cloud-plugin-creds")
pwShard := cmd.String("shard") pwShard := cmd.String("shard")
resultsDir := cmd.String("results-dir") resultsDir := cmd.String("results-dir")
htmlDir := cmd.String("html-dir") htmlDir := cmd.String("html-dir")
blobDir := cmd.String("blob-dir") blobDir := cmd.String("blob-dir")
playwrightCommand := cmd.String("playwright-command")
// pa11yConfigPath := cmd.String("config") // pa11yConfigPath := cmd.String("config")
// pa11yResultsPath := cmd.String("results") // pa11yResultsPath := cmd.String("results")
// noThresholdFail := cmd.Bool("no-threshold-fail") // noThresholdFail := cmd.Bool("no-threshold-fail")
@ -156,6 +169,11 @@ func run(ctx context.Context, cmd *cli.Command) error {
license = d.Host().File(licensePath) license = d.Host().File(licensePath)
} }
var cloudPluginCreds *dagger.File
if cloudPluginCredsPath != "" {
cloudPluginCreds = d.Host().File(cloudPluginCredsPath)
}
svc, err := GrafanaService(ctx, d, GrafanaServiceOpts{ svc, err := GrafanaService(ctx, d, GrafanaServiceOpts{
HostSrc: grafanaHostSrc, HostSrc: grafanaHostSrc,
FrontendContainer: frontendContainer, FrontendContainer: frontendContainer,
@ -174,6 +192,8 @@ func run(ctx context.Context, cmd *cli.Command) error {
TestResultsExportDir: resultsDir, TestResultsExportDir: resultsDir,
HTMLReportExportDir: htmlDir, HTMLReportExportDir: htmlDir,
BlobReportExportDir: blobDir, BlobReportExportDir: blobDir,
PlaywrightCommand: playwrightCommand,
CloudPluginCreds: cloudPluginCreds,
} }
c, runErr := RunTest(ctx, d, runOpts) c, runErr := RunTest(ctx, d, runOpts)