[SLO] Write functional tests for slo embeddables (#182568)

Fixes https://github.com/elastic/kibana/issues/182637

## Summary

This PR introduces our first functional tests for the SLO Overview
Embeddable with a bunch of helper functions to interact with the
different UI elements. Here are the basic scenarios that have been
covered:
- Add a single SLO Overview panel
  - Verify the configuration flyout opens
  - Verify the SLO selection dropdown exists
  - Verify the Save button is enabled
  - Verify an SLO Overview panel is added
  - Verify the title is present
- Add a group SLO Overview panel
- Verify the overview mode selector exists and opens the group
configuration screen
  - Verify the Group by dropdown exists
  - Verify the Group dropdown exists
  - Verify the KQL bar exists
  - Verify an SLO Group Overview panel is added


Here's a list of scenarios that need to be covered in a follow up PR (I
will create a separate ticket with following acceptance criteria):
- Check that clicking on the SLO card item opens the SLO details flyout
- Check that clicking on the group expands the accordion and shows the
respective SLOs
- Check that `Edit criteria` link exists in the Group embeddable
- Interact with edit criteria link
  - Check that selected group by is shown
  - Check that selected group is shown
  - Check that custom filter is shown in the kql bar
- With Remote enabled

## How to test

```
node scripts/functional_tests_server.js --config x-pack/test/functional/apps/slo/embeddables/config.ts

node scripts/functional_test_runner --config=x-pack/test/functional/apps/slo/embeddables/config.ts

```
This commit is contained in:
Panagiota Mitsopoulou 2024-05-07 22:25:47 +02:00 committed by GitHub
parent 8c20433bd0
commit 7536b91e33
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
11 changed files with 280 additions and 8 deletions

View file

@ -321,6 +321,7 @@ enabled:
- x-pack/test/functional/apps/saved_objects_management/config.ts
- x-pack/test/functional/apps/saved_query_management/config.ts
- x-pack/test/functional/apps/security/config.ts
- x-pack/test/functional/apps/slo/embeddables/config.ts
- x-pack/test/functional/apps/snapshot_restore/config.ts
- x-pack/test/functional/apps/spaces/config.ts
- x-pack/test/functional/apps/spaces/in_solution_navigation/config.ts

View file

@ -171,7 +171,7 @@ export function SloGroupFilters({ selectedFilters, onSelected }: Props) {
>
<EuiSelect
fullWidth
data-test-subj="o11ySloGroupConfigurationSelect"
data-test-subj="sloGroupOverviewConfigurationGroupBy"
options={groupByOptions}
value={selectedGroupBy}
onChange={(e) => {
@ -207,7 +207,7 @@ export function SloGroupFilters({ selectedFilters, onSelected }: Props) {
defaultMessage: 'Select a {selectedGroupByLabel}',
values: { selectedGroupByLabel },
})}
data-test-subj="sloGroup"
data-test-subj="sloGroupOverviewConfigurationGroup"
options={groupOptions}
selectedOptions={selectedGroupOptions}
async
@ -233,6 +233,7 @@ export function SloGroupFilters({ selectedFilters, onSelected }: Props) {
}
>
<SearchBar
dataTestSubj="sloGroupOverviewConfigurationKqlBar"
appName={sloAppId}
placeholder={PLACEHOLDER}
indexPatterns={dataView ? [dataView] : []}

View file

@ -39,6 +39,7 @@ export interface OverviewModeSelectorProps {
export function OverviewModeSelector({ value, onChange }: OverviewModeSelectorProps) {
return (
<EuiButtonGroup
data-test-subj="sloOverviewModeSelector"
isFullWidth
legend="This is a basic group"
options={overviewModeOptions}

View file

@ -73,7 +73,7 @@ function SingleSloConfiguration({ overviewMode, onCreate, onCancel }: SingleConf
<EuiFlexGroup>
<EuiFlexItem>
<EuiFlexGroup>
<EuiFlexItem grow>
<EuiFlexItem data-test-subj="singleSloSelector" grow>
<SloSelector
singleSelection={true}
hasError={hasError}
@ -157,7 +157,7 @@ function GroupSloConfiguration({
return (
<>
<EuiFlyoutBody className="sloOverviewEmbeddable">
<EuiFlyoutBody data-test-subj="sloGroupOverviewConfiguration">
<EuiFlexGroup>
<EuiFlexItem>
<EuiFlexGroup>
@ -200,7 +200,7 @@ export function SloConfiguration({ initialInput, onCreate, onCancel }: SloConfig
);
return (
<EuiFlyout onClose={onCancel}>
<EuiFlyout data-test-subj="sloSingleOverviewConfiguration" onClose={onCancel}>
<EuiFlyoutHeader>
<EuiFlexGroup direction="column">
<EuiFlexItem>

View file

@ -138,6 +138,7 @@ export const getOverviewEmbeddableFactory = (deps: SloEmbeddableDeps) => {
return (
<Wrapper>
<EuiFlexGroup
data-test-subj="sloGroupOverviewPanel"
data-shared-item=""
justifyContent="flexEnd"
wrap
@ -154,7 +155,7 @@ export const getOverviewEmbeddableFactory = (deps: SloEmbeddableDeps) => {
embeddable: api,
} as ActionExecutionContext);
}}
data-test-subj="o11ySloAlertsWrapperSlOsIncludedLink"
data-test-subj="o11ySloOverviewEditCriteriaLink"
>
{i18n.translate('xpack.slo.overviewEmbeddable.editCriteriaLabel', {
defaultMessage: 'Edit criteria',

View file

@ -105,7 +105,7 @@ export function SloOverview({ sloId, sloInstanceId, remoteName, reloadSubject }:
const historicalSliData = formatHistoricalData(historicalSummary, 'sli_value');
return (
<div data-shared-item="" style={{ width: '100%' }}>
<div data-test-subj="sloSingleOverviewPanel" data-shared-item="" style={{ width: '100%' }}>
<SloCardChart
slo={slo}
historicalSliData={historicalSliData ?? []}

View file

@ -0,0 +1,17 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { FtrConfigProviderContext } from '@kbn/test';
export default async function ({ readConfigFile }: FtrConfigProviderContext) {
const functionalConfig = await readConfigFile(require.resolve('../../../config.base.js'));
return {
...functionalConfig.getAll(),
testFiles: [require.resolve('./overview_embeddable')],
};
}

View file

@ -0,0 +1,95 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { cleanup } from '@kbn/infra-forge';
import { loadTestData } from '../../../../api_integration/apis/slos/helper/load_test_data';
import { SloEsClient } from '../../../../api_integration/apis/slos/helper/es';
import { sloData } from '../../../../api_integration/apis/slos/fixtures/create_slo';
import { FtrProviderContext } from '../../../ftr_provider_context';
export default function ({ getService, getPageObjects }: FtrProviderContext) {
const PageObjects = getPageObjects(['common', 'dashboard']);
const esClient = getService('es');
const sloEsClient = new SloEsClient(esClient);
const logger = getService('log');
const slo = getService('slo');
const sloUi = getService('sloUi');
const dashboardAddPanel = getService('dashboardAddPanel');
describe('overview embeddable', function () {
before(async () => {
await loadTestData(getService);
await slo.deleteAllSLOs();
await slo.create(sloData);
await PageObjects.dashboard.navigateToApp();
await PageObjects.dashboard.clickNewDashboard();
await PageObjects.dashboard.switchToEditMode();
});
after(async () => {
await slo.deleteAllSLOs();
await cleanup({ esClient, logger });
await sloEsClient.deleteTestSourceData();
});
describe('Single SLO', function () {
it('should open SLO configuration flyout', async () => {
await dashboardAddPanel.clickEditorMenuButton();
await dashboardAddPanel.clickEmbeddableFactoryGroupButton('slos');
await dashboardAddPanel.clickAddNewPanelFromUIActionLink('SLO Overview');
await sloUi.common.assertSloOverviewConfigurationExists();
});
it('should have an overview mode selector', async () => {
await sloUi.common.assertOverviewModeSelectorExists();
});
it('can select an SLO', async () => {
await sloUi.common.assertOverviewSloSelectorExists();
await sloUi.common.setComboBoxSloSelection();
await sloUi.common.clickOverviewCofigurationSaveButton();
});
it('creates an overview panel', async () => {
await sloUi.common.assertSingleOverviewPanelExists();
await sloUi.common.dismissAllToasts();
});
it('renders SLO card item chart', async () => {
await sloUi.common.assertSingleOverviewPanelContentExists();
});
});
describe('Group of SLOs', function () {
it('can select Group Overview mode in the Flyout configuration', async () => {
await dashboardAddPanel.clickEditorMenuButton();
await dashboardAddPanel.clickEmbeddableFactoryGroupButton('slos');
await dashboardAddPanel.clickAddNewPanelFromUIActionLink('SLO Overview');
await sloUi.common.clickOverviewMode();
await sloUi.common.assertSloConfigurationGroupOverviewModeIsSelected();
});
it('can select a group by', async () => {
await sloUi.common.assertGroupOverviewConfigurationGroupByExists();
});
it('can optionally select a group', async () => {
await sloUi.common.assertGroupOverviewConfigurationGroupExists();
});
it('can optionally search for custom query', async () => {
await sloUi.common.assertGroupOverviewConfigurationKqlBarExists();
});
it('creates a group overview panel', async () => {
await sloUi.common.clickOverviewCofigurationSaveButton();
await sloUi.common.assertGroupOverviewPanelExists();
await sloUi.common.dismissAllToasts();
});
});
});
}

View file

@ -69,7 +69,7 @@ import { RulesServiceProvider } from './rules';
import { AiopsProvider } from './aiops';
import { SampleDataServiceProvider } from './sample_data';
import { DataStreamProvider } from './data_stream';
import { SloUiServiceProvider } from './slo';
// define the name and providers for services that should be
// available to your tests. If you don't specify anything here
// only the built-in services will be available
@ -131,4 +131,7 @@ export const services = {
aiops: AiopsProvider,
sampleData: SampleDataServiceProvider,
dataStreams: DataStreamProvider,
slo: kibanaXPackApiIntegrationServices.slo,
dataViewApi: kibanaXPackApiIntegrationServices.dataViewApi,
sloUi: SloUiServiceProvider,
};

View file

@ -0,0 +1,138 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import expect from '@kbn/expect';
import { FtrProviderContext } from '../../ftr_provider_context';
import { sloData } from '../../../api_integration/apis/slos/fixtures/create_slo';
const OVERVIEW_MODE_SELECTOR = 'sloOverviewModeSelector';
const SLO_CONFIRM_BUTTON = 'sloConfirmButton';
const SINGLE_SLO_SELECTOR = 'singleSloSelector';
const SLO_SINGLE_OVERVIEW_CONFIGURATION = 'sloSingleOverviewConfiguration';
const SLO_GROUP_OVERVIEW_CONFIGURATION = 'sloGroupOverviewConfiguration';
const SLO_GROUP_OVERVIEW_CONFIGURATION_GROUP_BY = 'sloGroupOverviewConfigurationGroupBy';
const SLO_GROUP_OVERVIEW_CONFIGURATION_GROUP = 'sloGroupOverviewConfigurationGroup';
const SLO_GROUP_OVERVIEW_CONFIGURATION_KQLBAR = 'sloGroupOverviewConfigurationKqlBar';
const SLO_SINGLE_OVERVIEW_PANEL = 'sloSingleOverviewPanel';
const SLO_GROUP_OVERVIEW_PANEL = 'sloGroupOverviewPanel';
export function SloUiCommonServiceProvider({ getService }: FtrProviderContext) {
const testSubjects = getService('testSubjects');
const comboBox = getService('comboBox');
const find = getService('find');
const toasts = getService('toasts');
const retry = getService('retry');
return {
async assertSloOverviewConfigurationExists() {
await retry.tryForTime(60 * 1000, async () => {
await testSubjects.existOrFail(SLO_SINGLE_OVERVIEW_CONFIGURATION);
});
},
async assertOverviewSloSelectorExists() {
await retry.tryForTime(60 * 1000, async () => {
await testSubjects.existOrFail(SINGLE_SLO_SELECTOR);
});
},
async setComboBoxSloSelection() {
await retry.tryForTime(60 * 1000, async () => {
await testSubjects.click('sloSelector');
await comboBox.set('sloSelector > comboBoxInput', sloData.name);
});
},
async assertOverviewConfigurationSaveButtonIsEnabled(subj: string) {
await retry.tryForTime(60 * 1000, async () => {
await testSubjects.existOrFail(subj);
await testSubjects.isEnabled(subj);
});
},
async clickOverviewCofigurationSaveButton() {
await retry.tryForTime(60 * 1000, async () => {
await this.assertOverviewConfigurationSaveButtonIsEnabled(SLO_CONFIRM_BUTTON);
await testSubjects.clickWhenNotDisabledWithoutRetry(SLO_CONFIRM_BUTTON);
});
},
async assertOverviewModeSelectorExists() {
await retry.tryForTime(60 * 1000, async () => {
await testSubjects.existOrFail(OVERVIEW_MODE_SELECTOR);
});
},
async clickOverviewMode() {
await retry.tryForTime(60 * 1000, async () => {
await this.assertOverviewModeSelectorExists();
await testSubjects.click(OVERVIEW_MODE_SELECTOR);
});
},
async assertSloConfigurationGroupOverviewModeIsSelected() {
await retry.tryForTime(60 * 1000, async () => {
await testSubjects.existOrFail(SLO_GROUP_OVERVIEW_CONFIGURATION);
});
},
async assertGroupOverviewConfigurationGroupByExists() {
await retry.tryForTime(60 * 1000, async () => {
await testSubjects.existOrFail(SLO_GROUP_OVERVIEW_CONFIGURATION_GROUP_BY);
});
},
async assertGroupOverviewConfigurationGroupExists() {
await retry.tryForTime(60 * 1000, async () => {
await testSubjects.existOrFail(SLO_GROUP_OVERVIEW_CONFIGURATION_GROUP);
});
},
async assertGroupOverviewConfigurationKqlBarExists() {
await retry.tryForTime(60 * 1000, async () => {
await testSubjects.existOrFail(SLO_GROUP_OVERVIEW_CONFIGURATION_KQLBAR);
});
},
async dismissAllToasts() {
await retry.tryForTime(60 * 1000, async () => {
await toasts.dismissAll();
});
},
async assertSingleOverviewPanelExists() {
await retry.tryForTime(60 * 1000, async () => {
await testSubjects.existOrFail(SLO_SINGLE_OVERVIEW_PANEL);
});
},
async getSloCardTitle() {
const container = await testSubjects.find(SLO_SINGLE_OVERVIEW_PANEL);
return await (await container.findByClassName('echMetricText__title')).getVisibleText();
},
async assertSingleOverviewPanelContentExists() {
await retry.tryForTime(2000, async () => {
expect(
await find.existsByCssSelector(
`[data-test-subj="${SLO_SINGLE_OVERVIEW_PANEL}"] .echChart`
)
).to.eql(true);
expect(
await find.existsByCssSelector(
`[data-test-subj="${SLO_SINGLE_OVERVIEW_PANEL}"] .echMetricText__title`
)
).to.eql(true);
expect(await this.getSloCardTitle()).to.eql(sloData.name);
});
},
async assertGroupOverviewPanelExists() {
await retry.tryForTime(60 * 1000, async () => {
await testSubjects.existOrFail(SLO_GROUP_OVERVIEW_PANEL);
});
},
};
}

View file

@ -0,0 +1,15 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { FtrProviderContext } from '../../ftr_provider_context';
import { SloUiCommonServiceProvider } from './common';
export function SloUiServiceProvider(context: FtrProviderContext) {
return {
common: SloUiCommonServiceProvider(context),
};
}