diff --git a/.buildkite/scout_ci_config.yml b/.buildkite/scout_ci_config.yml
index 60dcde0393d66..d28bbf624b5df 100644
--- a/.buildkite/scout_ci_config.yml
+++ b/.buildkite/scout_ci_config.yml
@@ -4,6 +4,7 @@ plugins:
- apm
- console
- discover_enhanced
+ - index_management
- maps
- observability
- observability_onboarding
diff --git a/x-pack/platform/plugins/shared/index_management/__jest__/client_integration/helpers/test_subjects.ts b/x-pack/platform/plugins/shared/index_management/__jest__/client_integration/helpers/test_subjects.ts
index f4505c4fe56b7..fa2ab6e79bb11 100644
--- a/x-pack/platform/plugins/shared/index_management/__jest__/client_integration/helpers/test_subjects.ts
+++ b/x-pack/platform/plugins/shared/index_management/__jest__/client_integration/helpers/test_subjects.ts
@@ -119,4 +119,9 @@ export type TestSubjects =
| 'clearIndicesSearch'
| 'usingMaxRetention'
| 'componentTemplatesLink'
+ | 'summaryTabBtn'
+ | 'settingsTabBtn'
+ | 'mappingsTabBtn'
+ | 'aliasesTabBtn'
+ | 'previewTabBtn'
| 'configureFailureStoreButton';
diff --git a/x-pack/platform/plugins/shared/index_management/__jest__/client_integration/home/index_templates_tab.helpers.ts b/x-pack/platform/plugins/shared/index_management/__jest__/client_integration/home/index_templates_tab.helpers.ts
index 049015ea1fb57..dbdcbac4de945 100644
--- a/x-pack/platform/plugins/shared/index_management/__jest__/client_integration/home/index_templates_tab.helpers.ts
+++ b/x-pack/platform/plugins/shared/index_management/__jest__/client_integration/home/index_templates_tab.helpers.ts
@@ -40,10 +40,10 @@ const createActions = (testBed: TestBed) => {
const selectDetailsTab = async (
tab: 'summary' | 'settings' | 'mappings' | 'aliases' | 'preview'
) => {
- const tabs = ['summary', 'settings', 'mappings', 'aliases', 'preview'];
+ const tabTestDataSubj = `${tab}TabBtn` as TestSubjects;
await act(async () => {
- testBed.find('templateDetails.tab').at(tabs.indexOf(tab)).simulate('click');
+ testBed.find(tabTestDataSubj).simulate('click');
});
testBed.component.update();
};
diff --git a/x-pack/platform/plugins/shared/index_management/__jest__/client_integration/home/index_templates_tab.test.ts b/x-pack/platform/plugins/shared/index_management/__jest__/client_integration/home/index_templates_tab.test.ts
index 6647fea1cc4e2..34a56cffcb531 100644
--- a/x-pack/platform/plugins/shared/index_management/__jest__/client_integration/home/index_templates_tab.test.ts
+++ b/x-pack/platform/plugins/shared/index_management/__jest__/client_integration/home/index_templates_tab.test.ts
@@ -624,14 +624,11 @@ describe('Index Templates tab', () => {
await actions.clickTemplateAt(0);
- expect(find('templateDetails.tab').length).toBe(5);
- expect(find('templateDetails.tab').map((t) => t.text())).toEqual([
- 'Summary',
- 'Settings',
- 'Mappings',
- 'Aliases',
- 'Preview',
- ]);
+ expect(exists('summaryTabBtn')).toBe(true);
+ expect(exists('settingsTabBtn')).toBe(true);
+ expect(exists('mappingsTabBtn')).toBe(true);
+ expect(exists('aliasesTabBtn')).toBe(true);
+ expect(exists('previewTabBtn')).toBe(true);
// Summary tab should be initial active tab
expect(exists('summaryTab')).toBe(true);
@@ -671,7 +668,7 @@ describe('Index Templates tab', () => {
isLegacy: true,
});
- const { actions, find, exists } = testBed;
+ const { actions, exists } = testBed;
httpRequestsMockHelpers.setLoadTemplateResponse(
templates[0].name,
@@ -679,7 +676,6 @@ describe('Index Templates tab', () => {
);
await actions.clickTemplateAt(0);
- expect(find('templateDetails.tab').length).toBe(5);
expect(exists('summaryTab')).toBe(true);
// Navigate and verify callout message per tab
diff --git a/x-pack/platform/plugins/shared/index_management/__jest__/client_integration/index_template_wizard/template_create.test.tsx b/x-pack/platform/plugins/shared/index_management/__jest__/client_integration/index_template_wizard/template_create.test.tsx
index e58bcbf951a17..63718bd8e4c69 100644
--- a/x-pack/platform/plugins/shared/index_management/__jest__/client_integration/index_template_wizard/template_create.test.tsx
+++ b/x-pack/platform/plugins/shared/index_management/__jest__/client_integration/index_template_wizard/template_create.test.tsx
@@ -390,21 +390,14 @@ describe('', () => {
});
describe('plugin parameters', () => {
- const selectMappingsEditorTab = async (
- tab: 'fields' | 'runtimeFields' | 'templates' | 'advanced'
- ) => {
- const tabIndex = ['fields', 'runtimeFields', 'templates', 'advanced'].indexOf(tab);
- const tabElement = testBed.find('mappingsEditor.formTab').at(tabIndex);
- await act(async () => {
- tabElement.simulate('click');
- });
- testBed.component.update();
- };
-
test('should not render the _size parameter if the mapper size plugin is not installed', async () => {
const { exists } = testBed;
// Navigate to the advanced configuration
- await selectMappingsEditorTab('advanced');
+
+ await act(async () => {
+ testBed.find('advancedOptionsTab').simulate('click');
+ });
+ testBed.component.update();
expect(exists('mappingsEditor.advancedConfiguration.sizeEnabledToggle')).toBe(false);
});
@@ -418,7 +411,10 @@ describe('', () => {
testBed.component.update();
await navigateToMappingsStep();
- await selectMappingsEditorTab('advanced');
+ await act(async () => {
+ testBed.find('advancedOptionsTab').simulate('click');
+ });
+ testBed.component.update();
expect(testBed.exists('mappingsEditor.advancedConfiguration.sizeEnabledToggle')).toBe(
true
diff --git a/x-pack/platform/plugins/shared/index_management/__jest__/client_integration/index_template_wizard/template_form.helpers.ts b/x-pack/platform/plugins/shared/index_management/__jest__/client_integration/index_template_wizard/template_form.helpers.ts
index 9b4ed84052d30..43d2f58a62bf6 100644
--- a/x-pack/platform/plugins/shared/index_management/__jest__/client_integration/index_template_wizard/template_form.helpers.ts
+++ b/x-pack/platform/plugins/shared/index_management/__jest__/client_integration/index_template_wizard/template_form.helpers.ts
@@ -402,6 +402,6 @@ export type TestSubjects =
| 'valueDataRetentionField'
| 'formWizardStep-5'
| 'lifecycleValue'
- | 'mappingsEditor.formTab'
| 'mappingsEditor.advancedConfiguration.sizeEnabledToggle'
- | 'previewIndexTemplate';
+ | 'previewIndexTemplate'
+ | 'advancedOptionsTab';
diff --git a/x-pack/platform/plugins/shared/index_management/moon.yml b/x-pack/platform/plugins/shared/index_management/moon.yml
index e95accdbd32cf..ea7967d15b106 100644
--- a/x-pack/platform/plugins/shared/index_management/moon.yml
+++ b/x-pack/platform/plugins/shared/index_management/moon.yml
@@ -72,6 +72,7 @@ dependsOn:
- '@kbn/upgrade-assistant-pkg-common'
- '@kbn/failure-store-modal'
- '@kbn/react-query'
+ - '@kbn/scout'
tags:
- plugin
- prod
diff --git a/x-pack/platform/plugins/shared/index_management/public/application/components/mappings_editor/__jest__/client_integration/helpers/mappings_editor.helpers.tsx b/x-pack/platform/plugins/shared/index_management/public/application/components/mappings_editor/__jest__/client_integration/helpers/mappings_editor.helpers.tsx
index 02f7fb379f552..5b47ae51ab626 100644
--- a/x-pack/platform/plugins/shared/index_management/public/application/components/mappings_editor/__jest__/client_integration/helpers/mappings_editor.helpers.tsx
+++ b/x-pack/platform/plugins/shared/index_management/public/application/components/mappings_editor/__jest__/client_integration/helpers/mappings_editor.helpers.tsx
@@ -314,8 +314,9 @@ const createActions = (testBed: TestBed) => {
// --- Other ---
const selectTab = async (tab: 'fields' | 'runtimeFields' | 'templates' | 'advanced') => {
const index = ['fields', 'runtimeFields', 'templates', 'advanced'].indexOf(tab);
+ const dataTestSubjValues = ['fieldsTab', 'runtimeTab', 'templatesTab', 'advancedOptionsTab'];
- const tabElement = find('formTab').at(index);
+ const tabElement = find(dataTestSubjValues[index]);
if (tabElement.length === 0) {
throw new Error(`Tab not found: "${tab}"`);
}
diff --git a/x-pack/platform/plugins/shared/index_management/public/application/components/mappings_editor/__jest__/client_integration/mappings_editor.test.tsx b/x-pack/platform/plugins/shared/index_management/public/application/components/mappings_editor/__jest__/client_integration/mappings_editor.test.tsx
index 9d165da556b19..469709b0ff454 100644
--- a/x-pack/platform/plugins/shared/index_management/public/application/components/mappings_editor/__jest__/client_integration/mappings_editor.test.tsx
+++ b/x-pack/platform/plugins/shared/index_management/public/application/components/mappings_editor/__jest__/client_integration/mappings_editor.test.tsx
@@ -166,12 +166,10 @@ describe('Mappings editor: core', () => {
test('should have 4 tabs (fields, runtime, template, advanced settings)', () => {
const { find } = testBed;
- const tabs = find('formTab').map((wrapper) => wrapper.text());
-
- expect(tabs).toEqual([
- 'Mapped fields',
- 'Runtime fields',
- 'Dynamic templates',
+ expect(find('fieldsTab').map((wrapper) => wrapper.text())).toEqual(['Mapped fields']);
+ expect(find('runtimeTab').map((wrapper) => wrapper.text())).toEqual(['Runtime fields']);
+ expect(find('templatesTab').map((wrapper) => wrapper.text())).toEqual(['Dynamic templates']);
+ expect(find('advancedOptionsTab').map((wrapper) => wrapper.text())).toEqual([
'Advanced options',
]);
});
diff --git a/x-pack/platform/plugins/shared/index_management/public/application/components/mappings_editor/mappings_editor.tsx b/x-pack/platform/plugins/shared/index_management/public/application/components/mappings_editor/mappings_editor.tsx
index c1975d501969b..bf912d1fca1d4 100644
--- a/x-pack/platform/plugins/shared/index_management/public/application/components/mappings_editor/mappings_editor.tsx
+++ b/x-pack/platform/plugins/shared/index_management/public/application/components/mappings_editor/mappings_editor.tsx
@@ -184,7 +184,7 @@ export const MappingsEditor = React.memo(
changeTab('fields')}
isSelected={selectedTab === 'fields'}
- data-test-subj="formTab"
+ data-test-subj="fieldsTab"
>
{i18n.translate('xpack.idxMgmt.mappingsEditor.fieldsTabLabel', {
defaultMessage: 'Mapped fields',
@@ -193,7 +193,7 @@ export const MappingsEditor = React.memo(
changeTab('runtimeFields')}
isSelected={selectedTab === 'runtimeFields'}
- data-test-subj="formTab"
+ data-test-subj="runtimeTab"
>
{i18n.translate('xpack.idxMgmt.mappingsEditor.runtimeFieldsTabLabel', {
defaultMessage: 'Runtime fields',
@@ -202,7 +202,7 @@ export const MappingsEditor = React.memo(
changeTab('templates')}
isSelected={selectedTab === 'templates'}
- data-test-subj="formTab"
+ data-test-subj="templatesTab"
>
{i18n.translate('xpack.idxMgmt.mappingsEditor.templatesTabLabel', {
defaultMessage: 'Dynamic templates',
@@ -211,7 +211,7 @@ export const MappingsEditor = React.memo(
changeTab('advanced')}
isSelected={selectedTab === 'advanced'}
- data-test-subj="formTab"
+ data-test-subj="advancedOptionsTab"
>
{i18n.translate('xpack.idxMgmt.mappingsEditor.advancedTabLabel', {
defaultMessage: 'Advanced options',
diff --git a/x-pack/platform/plugins/shared/index_management/public/application/components/no_match/no_match.tsx b/x-pack/platform/plugins/shared/index_management/public/application/components/no_match/no_match.tsx
index a96e4dce62f62..71673cdd24900 100644
--- a/x-pack/platform/plugins/shared/index_management/public/application/components/no_match/no_match.tsx
+++ b/x-pack/platform/plugins/shared/index_management/public/application/components/no_match/no_match.tsx
@@ -94,7 +94,13 @@ export const NoMatch = ({
/>
}
- actions={}
+ actions={
+
+ }
/>
);
};
diff --git a/x-pack/platform/plugins/shared/index_management/public/application/sections/home/template_list/template_details/template_details_content.tsx b/x-pack/platform/plugins/shared/index_management/public/application/sections/home/template_list/template_details/template_details_content.tsx
index 98ea3bf6b7549..e75980a053e6a 100644
--- a/x-pack/platform/plugins/shared/index_management/public/application/sections/home/template_list/template_details/template_details_content.tsx
+++ b/x-pack/platform/plugins/shared/index_management/public/application/sections/home/template_list/template_details/template_details_content.tsx
@@ -55,30 +55,35 @@ const TABS = [
name: i18n.translate('xpack.idxMgmt.templateDetails.summaryTabTitle', {
defaultMessage: 'Summary',
}),
+ dataTestSubj: 'summaryTabBtn',
},
{
id: SETTINGS_TAB_ID,
name: i18n.translate('xpack.idxMgmt.templateDetails.settingsTabTitle', {
defaultMessage: 'Settings',
}),
+ dataTestSubj: 'settingsTabBtn',
},
{
id: MAPPINGS_TAB_ID,
name: i18n.translate('xpack.idxMgmt.templateDetails.mappingsTabTitle', {
defaultMessage: 'Mappings',
}),
+ dataTestSubj: 'mappingsTabBtn',
},
{
id: ALIASES_TAB_ID,
name: i18n.translate('xpack.idxMgmt.templateDetails.aliasesTabTitle', {
defaultMessage: 'Aliases',
}),
+ dataTestSubj: 'aliasesTabBtn',
},
{
id: PREVIEW_TAB_ID,
name: i18n.translate('xpack.idxMgmt.templateDetails.previewTabTitle', {
defaultMessage: 'Preview',
}),
+ dataTestSubj: 'previewTabBtn',
},
];
@@ -217,7 +222,7 @@ export const TemplateDetailsContent = ({
}}
isSelected={tab.id === activeTab}
key={tab.id}
- data-test-subj="tab"
+ data-test-subj={tab.dataTestSubj}
>
{tab.name}
diff --git a/x-pack/platform/plugins/shared/index_management/test/scout/ui/fixtures/custom_roles.ts b/x-pack/platform/plugins/shared/index_management/test/scout/ui/fixtures/custom_roles.ts
new file mode 100644
index 0000000000000..b77fa5f82f7c7
--- /dev/null
+++ b/x-pack/platform/plugins/shared/index_management/test/scout/ui/fixtures/custom_roles.ts
@@ -0,0 +1,30 @@
+/*
+ * 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 type { KibanaRole } from '@kbn/scout';
+
+export const CUSTOM_ROLES: Record = {
+ indexManagementUser: {
+ elasticsearch: {
+ // would be nice if this wasn't needed
+ cluster: ['monitor', 'manage_index_templates', 'manage_enrich'],
+ indices: [
+ {
+ names: ['*'],
+ privileges: ['all'],
+ },
+ ],
+ },
+ kibana: [
+ {
+ base: ['read'],
+ feature: {},
+ spaces: ['*'],
+ },
+ ],
+ },
+};
diff --git a/x-pack/platform/plugins/shared/index_management/test/scout/ui/fixtures/index.ts b/x-pack/platform/plugins/shared/index_management/test/scout/ui/fixtures/index.ts
new file mode 100644
index 0000000000000..1b7b0ab8e4348
--- /dev/null
+++ b/x-pack/platform/plugins/shared/index_management/test/scout/ui/fixtures/index.ts
@@ -0,0 +1,84 @@
+/*
+ * 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 { test as base } from '@kbn/scout';
+import type {
+ ScoutPage,
+ ScoutTestFixtures,
+ ScoutWorkerFixtures,
+ BrowserAuthFixture,
+} from '@kbn/scout';
+import type { PageObjects } from '@kbn/scout';
+import { createLazyPageObject } from '@kbn/scout';
+import type { AbstractPageObject } from './page_objects/index_management_page';
+import { IndexManagement } from './page_objects/index_management_page';
+import { CUSTOM_ROLES } from './custom_roles';
+
+type PageObjectClass = new (page: ScoutPage) => AbstractPageObject;
+
+export interface IndexManagementBrowserAuthFixture extends BrowserAuthFixture {
+ loginAsIndexManagementUser: () => Promise;
+}
+
+export const createTest = function >(
+ pageObjectClassMap: Record
+) {
+ type PageObjectsExtended = PageObjectsExtensions & PageObjects;
+
+ function extendPOs(pageObjects: PageObjects, page: ScoutPage): PageObjectsExtended {
+ const initedLazyPageObjects = Object.keys(pageObjectClassMap).reduce<
+ Record
+ >((col, value) => {
+ col[value] = createLazyPageObject(pageObjectClassMap[value], page);
+ return col;
+ }, {});
+
+ return {
+ ...initedLazyPageObjects,
+ ...pageObjects,
+ } as PageObjectsExtended;
+ }
+
+ return base.extend<
+ ScoutTestFixtures & {
+ pageObjects: PageObjectsExtended;
+ },
+ ScoutWorkerFixtures & {
+ browserAuth: IndexManagementBrowserAuthFixture;
+ }
+ >({
+ pageObjects: async (
+ {
+ pageObjects,
+ page,
+ }: {
+ pageObjects: PageObjectsExtended;
+ page: ScoutPage;
+ },
+ use: (pageObjects: PageObjectsExtended) => Promise
+ ) => {
+ const extendedPageObjects = extendPOs(pageObjects, page);
+ await use(extendedPageObjects);
+ },
+ browserAuth: async (
+ { browserAuth }: { browserAuth: BrowserAuthFixture },
+ use: (browserAuth: IndexManagementBrowserAuthFixture) => Promise
+ ) => {
+ const loginAsIndexManagementUser = async () =>
+ browserAuth.loginWithCustomRole(CUSTOM_ROLES.indexManagementUser);
+
+ await use({
+ ...browserAuth,
+ loginAsIndexManagementUser,
+ });
+ },
+ });
+};
+
+export const test = createTest<{ indexManagement: IndexManagement }>({
+ indexManagement: IndexManagement,
+});
diff --git a/x-pack/platform/plugins/shared/index_management/test/scout/ui/fixtures/page_objects/index.ts b/x-pack/platform/plugins/shared/index_management/test/scout/ui/fixtures/page_objects/index.ts
new file mode 100644
index 0000000000000..ef6d312f3a37f
--- /dev/null
+++ b/x-pack/platform/plugins/shared/index_management/test/scout/ui/fixtures/page_objects/index.ts
@@ -0,0 +1,24 @@
+/*
+ * 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 type { PageObjects, ScoutPage } from '@kbn/scout';
+import { createLazyPageObject } from '@kbn/scout';
+import { IndexManagement } from './index_management_page';
+
+export interface IndexManagementPageObjects extends PageObjects {
+ indexManagement: IndexManagement;
+}
+
+export function extendPageObjects(
+ pageObjects: PageObjects,
+ page: ScoutPage
+): IndexManagementPageObjects {
+ return {
+ ...pageObjects,
+ indexManagement: createLazyPageObject(IndexManagement, page),
+ };
+}
diff --git a/x-pack/platform/plugins/shared/index_management/test/scout/ui/fixtures/page_objects/index_management_page.ts b/x-pack/platform/plugins/shared/index_management/test/scout/ui/fixtures/page_objects/index_management_page.ts
new file mode 100644
index 0000000000000..689ee99c10a78
--- /dev/null
+++ b/x-pack/platform/plugins/shared/index_management/test/scout/ui/fixtures/page_objects/index_management_page.ts
@@ -0,0 +1,145 @@
+/*
+ * 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.
+ */
+
+/* eslint-disable max-classes-per-file */
+
+import { type ScoutPage, expect, EuiFieldTextWrapper } from '@kbn/scout';
+
+export class AbstractPageObject {
+ constructor(public readonly page: ScoutPage) {}
+}
+
+export class IndexManagement extends AbstractPageObject {
+ async goto() {
+ await this.page.gotoApp('management/data/index_management');
+ }
+
+ async sectionHeadingText() {
+ return await this.page.testSubj.locator('appTitle').textContent();
+ }
+
+ async changeTabs(
+ tab:
+ | 'indicesTab'
+ | 'data_streamsTab'
+ | 'templatesTab'
+ | 'component_templatesTab'
+ | 'enrich_policiesTab'
+ ) {
+ await this.page.testSubj.locator(tab).click();
+ }
+
+ async clickCreateIndexButton() {
+ await this.page.testSubj.locator('createIndexButton').click();
+ }
+
+ async setCreateIndexName(value: string) {
+ await this.page.testSubj.fill('createIndexNameFieldText', value);
+ }
+
+ async setCreateIndexMode(value: string) {
+ await this.page.testSubj.click('indexModeField');
+ await this.page.testSubj.locator(`indexMode${value}Option`).click();
+ }
+
+ async clickCreateIndexSaveButton() {
+ const saveButton = this.page.testSubj.locator('createIndexSaveButton');
+ await saveButton.click();
+ // Wait for modal to close using web-first assertion
+ await expect(saveButton).toBeHidden({ timeout: 30000 });
+ }
+
+ /**
+ * Returns locator for index link - caller should use web-first assertions.
+ * Note: Consider using extended timeout as this can be slow in CI environments.
+ * Example: await expect(pageObjects.indexManagement.indexLink(name)).toBeVisible({ timeout: 30000 });
+ */
+ indexLink(indexName: string) {
+ return this.page.getByRole('button').getByText(indexName);
+ }
+
+ async toggleHiddenIndices() {
+ await this.page.testSubj.locator('checkboxToggles-includeHiddenIndices').click();
+ }
+
+ async openIndexDetailsPage(indexOfRow: number) {
+ const indexLinks = this.page.testSubj.locator('indexTableIndexNameLink');
+ // this should be refactored to use data-test-subj on the table rows
+ // eslint-disable-next-line playwright/no-nth-methods
+ await indexLinks.nth(indexOfRow).click();
+
+ // Wait for index details page to load using web-first assertion
+ await expect(this.page.testSubj.locator('indexDetailsHeader')).toBeVisible();
+ }
+
+ async navigateToIndexManagementTab(
+ tab: 'indices' | 'data_streams' | 'templates' | 'component_templates' | 'enrich_policies'
+ ) {
+ await this.goto();
+ const tabMap = {
+ indices: 'indicesTab',
+ data_streams: 'data_streamsTab',
+ templates: 'templatesTab',
+ component_templates: 'component_templatesTab',
+ enrich_policies: 'enrich_policiesTab',
+ };
+ await this.page.testSubj.locator(tabMap[tab]).click();
+ }
+
+ async clickNextButton() {
+ await this.page.testSubj.locator('nextButton').click();
+ }
+
+ /**
+ * Custom combobox interaction for non-clearable comboboxes.
+ * Note: Cannot use EuiComboBoxWrapper here because the fieldType combobox
+ * has isClearable={false} (in type_parameter.tsx), and EuiComboBoxWrapper.selectSingleOption()
+ * attempts to clear() first, which fails when trying to click the non-existent comboBoxClearButton.
+ */
+ async setComboBox(testSubject: string, value: string) {
+ const comboBox = this.page.testSubj.locator(testSubject);
+ await comboBox.click();
+
+ // Type the value
+ const input = comboBox.locator('input');
+ await input.fill(value);
+
+ // Wait for and click the option
+ await this.page.locator(`[role="option"]`).filter({ hasText: value }).click();
+ }
+
+ async changeMappingsEditorTab(tab: 'fields' | 'advancedOptions' | 'templates') {
+ const tabMap = {
+ fields: 'fieldsTab',
+ advancedOptions: 'advancedOptionsTab',
+ templates: 'templatesTab',
+ };
+ await this.page.testSubj.locator(tabMap[tab]).click();
+ }
+
+ indexDetailsPage = {
+ expectIndexDetailsPageIsLoaded: async () => {
+ await expect(this.page.testSubj.locator('indexDetailsTab-overview')).toBeVisible();
+ await expect(this.page.testSubj.locator('indexDetailsContent')).toBeVisible();
+ await expect(this.page.testSubj.locator('indexDetailsBackToIndicesButton')).toBeVisible();
+ },
+ };
+
+ indexTemplateWizard = {
+ completeStepOne: async () => {
+ const nameField = new EuiFieldTextWrapper(this.page, { dataTestSubj: 'nameField' });
+ await nameField.fill('test-index-template');
+
+ const indexPatternsField = new EuiFieldTextWrapper(this.page, {
+ dataTestSubj: 'indexPatternsField',
+ });
+ await indexPatternsField.fill('test-index-pattern');
+
+ await this.clickNextButton();
+ },
+ };
+}
diff --git a/x-pack/platform/plugins/shared/index_management/test/scout/ui/playwright.config.ts b/x-pack/platform/plugins/shared/index_management/test/scout/ui/playwright.config.ts
new file mode 100644
index 0000000000000..75a7694d12043
--- /dev/null
+++ b/x-pack/platform/plugins/shared/index_management/test/scout/ui/playwright.config.ts
@@ -0,0 +1,12 @@
+/*
+ * 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 { createPlaywrightConfig } from '@kbn/scout';
+
+export default createPlaywrightConfig({
+ testDir: './tests',
+});
diff --git a/x-pack/platform/plugins/shared/index_management/test/scout/ui/tests/home_page.spec.ts b/x-pack/platform/plugins/shared/index_management/test/scout/ui/tests/home_page.spec.ts
new file mode 100644
index 0000000000000..49a02452ce3f4
--- /dev/null
+++ b/x-pack/platform/plugins/shared/index_management/test/scout/ui/tests/home_page.spec.ts
@@ -0,0 +1,113 @@
+/*
+ * 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/scout';
+import { test } from '../fixtures';
+
+const testIndexName = `index-test-${Math.random()}`;
+
+test.describe('Home page', { tag: ['@ess'] }, () => {
+ test.beforeEach(async ({ pageObjects, browserAuth }) => {
+ await browserAuth.loginAsIndexManagementUser();
+ await pageObjects.indexManagement.goto();
+ });
+
+ test.afterAll(async ({ esClient, log }) => {
+ try {
+ await esClient.indices.delete({ index: testIndexName });
+ } catch (e: any) {
+ log.debug(`Index cleanup failed for ${testIndexName}: ${e.message}`);
+ }
+ });
+
+ test('Loads the app and renders the indices tab by default', async ({
+ pageObjects,
+ log,
+ page,
+ }) => {
+ await log.debug('Checking for section heading to say Index Management.');
+
+ const headingText = await pageObjects.indexManagement.sectionHeadingText();
+ expect(headingText).toBe('Index Management');
+
+ // Verify url
+ expect(page.url()).toContain('/indices');
+
+ // Verify content
+ await expect(page.testSubj.locator('indicesList')).toBeVisible();
+ await expect(page.testSubj.locator('reloadIndicesButton')).toBeVisible();
+ });
+
+ test('Indices - renders the indices tab', async ({ pageObjects, page }) => {
+ // Navigate to the indices tab
+ await pageObjects.indexManagement.changeTabs('indicesTab');
+
+ // Verify url
+ expect(page.url()).toContain('/indices');
+
+ // Verify content - wait for table to be visible
+ await expect(page.testSubj.locator('indexTable')).toBeVisible();
+ });
+
+ test('Indices - can create an index', async ({ pageObjects }) => {
+ await pageObjects.indexManagement.clickCreateIndexButton();
+ await pageObjects.indexManagement.setCreateIndexName(testIndexName);
+ await pageObjects.indexManagement.setCreateIndexMode('Lookup');
+ await pageObjects.indexManagement.clickCreateIndexSaveButton();
+ await expect(pageObjects.indexManagement.indexLink(testIndexName)).toBeVisible({
+ timeout: 30000,
+ });
+ });
+
+ test('Data streams - renders the data streams tab', async ({ pageObjects, page }) => {
+ // Navigate to the data streams tab
+ await pageObjects.indexManagement.changeTabs('data_streamsTab');
+
+ // Verify url
+ expect(page.url()).toContain('/data_streams');
+
+ // Verify content - wait for table to be visible
+ await expect(page.testSubj.locator('dataStreamTable')).toBeVisible();
+ });
+
+ test('Index templates - renders the index templates tab', async ({ pageObjects, page }) => {
+ // Navigate to the index templates tab
+ await pageObjects.indexManagement.changeTabs('templatesTab');
+
+ // Verify url
+ expect(page.url()).toContain('/templates');
+
+ // Verify content
+ await expect(page.testSubj.locator('templateList')).toBeVisible();
+ });
+
+ test('Component templates - renders the component templates tab', async ({
+ pageObjects,
+ page,
+ }) => {
+ // Navigate to the component templates tab
+ await pageObjects.indexManagement.changeTabs('component_templatesTab');
+
+ // Verify url
+ expect(page.url()).toContain('/component_templates');
+
+ // Verify content. Component templates may have been created by other apps, e.g. Ingest Manager,
+ // so we don't make any assertion about the presence or absence of component templates.
+ await expect(page.testSubj.locator('componentTemplateList')).toBeVisible();
+ });
+
+ test('Enrich policies - renders the enrich policies tab', async ({ pageObjects, page }) => {
+ // Navigate to the enrich policies tab
+ await pageObjects.indexManagement.changeTabs('enrich_policiesTab');
+
+ // Verify url
+ expect(page.url()).toContain('/enrich_policies');
+
+ // Verify content
+ await expect(page.testSubj.locator('sectionEmpty')).toBeVisible();
+ });
+});
diff --git a/x-pack/platform/plugins/shared/index_management/test/scout/ui/tests/index_details_page.spec.ts b/x-pack/platform/plugins/shared/index_management/test/scout/ui/tests/index_details_page.spec.ts
new file mode 100644
index 0000000000000..1ddd2dc68aadb
--- /dev/null
+++ b/x-pack/platform/plugins/shared/index_management/test/scout/ui/tests/index_details_page.spec.ts
@@ -0,0 +1,28 @@
+/*
+ * 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 { test } from '../fixtures';
+
+test.describe('Index details page', { tag: ['@ess'] }, () => {
+ test.beforeEach(async ({ pageObjects, browserAuth }) => {
+ await browserAuth.loginAsIndexManagementUser();
+ await pageObjects.indexManagement.goto();
+ });
+
+ test('Navigates to the index details page from the home page', async ({ pageObjects, log }) => {
+ await log.debug('Navigating to the index details page');
+
+ // Display hidden indices to have some rows in the indices table
+ await pageObjects.indexManagement.toggleHiddenIndices();
+
+ // Click the first index in the table and wait for the index details page
+ await pageObjects.indexManagement.openIndexDetailsPage(0);
+
+ // Verify index details page is loaded
+ await pageObjects.indexManagement.indexDetailsPage.expectIndexDetailsPageIsLoaded();
+ });
+});
diff --git a/x-pack/platform/plugins/shared/index_management/test/scout/ui/tests/index_template_wizard.spec.ts b/x-pack/platform/plugins/shared/index_management/test/scout/ui/tests/index_template_wizard.spec.ts
new file mode 100644
index 0000000000000..8fed11f25349a
--- /dev/null
+++ b/x-pack/platform/plugins/shared/index_management/test/scout/ui/tests/index_template_wizard.spec.ts
@@ -0,0 +1,76 @@
+/*
+ * 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/scout';
+import { test } from '../fixtures';
+
+test.describe('Index template wizard - Create', { tag: ['@ess'] }, () => {
+ test.afterEach(async ({ esClient, log }) => {
+ try {
+ await esClient.indices.deleteIndexTemplate({ name: 'test-index-template' });
+ } catch (e) {
+ log.debug(
+ `Template cleanup failed for test-index-template: ${
+ e instanceof Error ? e.message : String(e)
+ }`
+ );
+ }
+ });
+
+ test('can walk through all wizard steps and create a template', async ({
+ page,
+ pageObjects,
+ browserAuth,
+ }) => {
+ await browserAuth.loginAsIndexManagementUser();
+ await pageObjects.indexManagement.navigateToIndexManagementTab('templates');
+ await page.testSubj.locator('createTemplateButton').click();
+
+ await test.step('verify page title', async () => {
+ await expect(page.testSubj.locator('pageTitle')).toBeVisible();
+ await expect(page.testSubj.locator('pageTitle')).toHaveText('Create template');
+ });
+
+ await test.step('1: Logistics', async () => {
+ await expect(page.testSubj.locator('stepTitle')).toHaveText('Logistics');
+ await pageObjects.indexManagement.indexTemplateWizard.completeStepOne();
+ });
+
+ await test.step('2: Component templates', async () => {
+ await expect(page.testSubj.locator('emptyPrompt')).toBeVisible();
+ await pageObjects.indexManagement.clickNextButton();
+ });
+
+ await test.step('3: Index settings', async () => {
+ await expect(page.testSubj.locator('stepTitle')).toHaveText('Index settings (optional)');
+ await expect(page.testSubj.locator('indexModeCallout')).toHaveText(
+ 'The index.mode setting has been set to Standard within the Logistics step. Any changes to index.mode set on this page will be overwritten by the Logistics selection.'
+ );
+ await pageObjects.indexManagement.clickNextButton();
+ });
+
+ await test.step('4: Mappings', async () => {
+ await expect(page.testSubj.locator('stepTitle')).toHaveText('Mappings (optional)');
+ await pageObjects.indexManagement.clickNextButton();
+ });
+
+ await test.step('5: Aliases', async () => {
+ await expect(page.testSubj.locator('stepTitle')).toHaveText('Aliases (optional)');
+ await pageObjects.indexManagement.clickNextButton();
+ });
+
+ await test.step('6: Review and create template', async () => {
+ await expect(page.testSubj.locator('stepTitle')).toHaveText(
+ "Review details for 'test-index-template'"
+ );
+ await expect(page.testSubj.locator('summaryTabContent')).toBeVisible();
+ await expect(page.testSubj.locator('indexModeTitle')).toBeVisible();
+ await expect(page.testSubj.locator('indexModeValue')).toHaveText('Standard');
+ await pageObjects.indexManagement.clickNextButton();
+ });
+ });
+});
diff --git a/x-pack/platform/plugins/shared/index_management/test/scout/ui/tests/index_template_wizard_mappings.spec.ts b/x-pack/platform/plugins/shared/index_management/test/scout/ui/tests/index_template_wizard_mappings.spec.ts
new file mode 100644
index 0000000000000..150bda219b6a2
--- /dev/null
+++ b/x-pack/platform/plugins/shared/index_management/test/scout/ui/tests/index_template_wizard_mappings.spec.ts
@@ -0,0 +1,121 @@
+/*
+ * 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, EuiFieldTextWrapper } from '@kbn/scout';
+import { test } from '../fixtures';
+
+test.describe('Index template wizard - Mappings step', { tag: ['@ess'] }, () => {
+ test.beforeEach(async ({ browserAuth, pageObjects, page }) => {
+ await browserAuth.loginAsIndexManagementUser();
+ await pageObjects.indexManagement.navigateToIndexManagementTab('templates');
+
+ // Click Create Template button
+ await page.testSubj.locator('createTemplateButton').click();
+
+ // Fill out required fields using EUI wrapper
+ const nameField = new EuiFieldTextWrapper(page, { dataTestSubj: 'nameField' });
+ await nameField.fill('test-index-template');
+ const indexPatternsField = new EuiFieldTextWrapper(page, {
+ dataTestSubj: 'indexPatternsField',
+ });
+ await indexPatternsField.fill('test-index-pattern');
+
+ // Go to Mappings step
+ await page.testSubj.locator('formWizardStep-3').click();
+ await expect(page.testSubj.locator('stepTitle')).toHaveText('Mappings (optional)');
+ });
+
+ test.afterEach(async ({ esClient, log }) => {
+ try {
+ await esClient.indices.deleteIndexTemplate({ name: 'test-index-template' });
+ } catch (e) {
+ log.debug(
+ `Template cleanup failed in mappings test: ${e instanceof Error ? e.message : String(e)}`
+ );
+ }
+ });
+
+ test("clearing up the Numeric subtype dropdown doesn't break the page", async ({
+ page,
+ pageObjects,
+ }) => {
+ // The Create Field form is already open by default when mappings are empty
+ // Select Numeric type
+ await pageObjects.indexManagement.setComboBox('fieldType', 'Numeric');
+
+ // Clear up subtype dropdown
+ await page.testSubj.locator('fieldSubType').click();
+ await page.keyboard.press('Backspace');
+
+ // Verify that elements are still visible
+ await expect(page.testSubj.locator('addFieldButton')).toBeVisible();
+ await expect(page.testSubj.locator('fieldType')).toBeVisible();
+ await expect(page.testSubj.locator('fieldSubType')).toBeVisible();
+ await expect(page.testSubj.locator('nextButton')).toBeVisible();
+ });
+
+ test("clearing up the Range subtype dropdown doesn't break the page", async ({
+ page,
+ pageObjects,
+ }) => {
+ // The Create Field form is already open by default when mappings are empty
+ // Select Range type
+ await pageObjects.indexManagement.setComboBox('fieldType', 'Range');
+
+ // Clear up subtype dropdown
+ await page.testSubj.locator('fieldSubType').click();
+ await page.keyboard.press('Backspace');
+
+ // Verify that elements are still visible
+ await expect(page.testSubj.locator('addFieldButton')).toBeVisible();
+ await expect(page.testSubj.locator('fieldType')).toBeVisible();
+ await expect(page.testSubj.locator('fieldSubType')).toBeVisible();
+ await expect(page.testSubj.locator('nextButton')).toBeVisible();
+ });
+
+ test("advanced options tab doesn't add default values to request by default", async ({
+ page,
+ pageObjects,
+ log,
+ }) => {
+ await pageObjects.indexManagement.changeMappingsEditorTab('advancedOptions');
+ await page.testSubj.locator('previewIndexTemplate').click();
+ const templatePreview = await page.testSubj.locator('simulateTemplatePreview').textContent();
+
+ log.debug(`Template preview text: ${templatePreview}`);
+
+ // All advanced options should not be part of the request
+ expect(templatePreview).not.toContain('"dynamic"');
+ expect(templatePreview).not.toContain('"subobjects"');
+ expect(templatePreview).not.toContain('"dynamic_date_formats"');
+ expect(templatePreview).not.toContain('"date_detection"');
+ expect(templatePreview).not.toContain('"numeric_detection"');
+ });
+
+ test('advanced options tab adds the set values to the request', async ({
+ page,
+ pageObjects,
+ log,
+ }) => {
+ await pageObjects.indexManagement.changeMappingsEditorTab('advancedOptions');
+
+ // Toggle the subobjects field to false
+ await page.testSubj.locator('subobjectsToggle').click();
+
+ await page.testSubj.locator('previewIndexTemplate').click();
+ const templatePreview = await page.testSubj.locator('simulateTemplatePreview').textContent();
+
+ log.debug(`Template preview text: ${templatePreview}`);
+
+ // Only the subobjects option should be part of the request
+ expect(templatePreview).toContain('"subobjects": false');
+ expect(templatePreview).not.toContain('"dynamic"');
+ expect(templatePreview).not.toContain('"dynamic_date_formats"');
+ expect(templatePreview).not.toContain('"date_detection"');
+ expect(templatePreview).not.toContain('"numeric_detection"');
+ });
+});
diff --git a/x-pack/platform/plugins/shared/index_management/test/scout/ui/tests/index_template_wizard_preview.spec.ts b/x-pack/platform/plugins/shared/index_management/test/scout/ui/tests/index_template_wizard_preview.spec.ts
new file mode 100644
index 0000000000000..d484a3056cb44
--- /dev/null
+++ b/x-pack/platform/plugins/shared/index_management/test/scout/ui/tests/index_template_wizard_preview.spec.ts
@@ -0,0 +1,77 @@
+/*
+ * 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, EuiFieldTextWrapper } from '@kbn/scout';
+import { test } from '../fixtures';
+
+test.describe('Index template wizard - Preview template', { tag: ['@ess'] }, () => {
+ test.afterEach(async ({ esClient, log }) => {
+ try {
+ await esClient.indices.deleteIndexTemplate({ name: 'a-star' });
+ } catch (e) {
+ log.debug(
+ `Template cleanup failed for a-star: ${e instanceof Error ? e.message : String(e)}`
+ );
+ }
+ });
+
+ test('can create and preview an index template', async ({ page, pageObjects, browserAuth }) => {
+ await browserAuth.loginAsIndexManagementUser();
+ await pageObjects.indexManagement.navigateToIndexManagementTab('templates');
+
+ await test.step('open wizard and fill logistics', async () => {
+ await page.testSubj.locator('createTemplateButton').click();
+ await expect(page.testSubj.locator('pageTitle')).toHaveText('Create template');
+ await expect(page.testSubj.locator('stepTitle')).toHaveText('Logistics');
+
+ const nameField = new EuiFieldTextWrapper(page, { dataTestSubj: 'nameField' });
+ await nameField.fill('a-star');
+ const indexPatternsField = new EuiFieldTextWrapper(page, {
+ dataTestSubj: 'indexPatternsField',
+ });
+ await indexPatternsField.fill('a*');
+ const priorityField = new EuiFieldTextWrapper(page, { dataTestSubj: 'priorityField' });
+ await priorityField.fill('1000');
+ await pageObjects.indexManagement.clickNextButton();
+ });
+
+ await test.step('skip component templates', async () => {
+ await expect(page.testSubj.locator('emptyPrompt')).toBeVisible();
+ await pageObjects.indexManagement.clickNextButton();
+ });
+
+ await test.step('skip index settings', async () => {
+ await expect(page.testSubj.locator('stepTitle')).toHaveText('Index settings (optional)');
+ await pageObjects.indexManagement.clickNextButton();
+ });
+
+ await test.step('skip mappings', async () => {
+ await expect(page.testSubj.locator('stepTitle')).toHaveText('Mappings (optional)');
+ await pageObjects.indexManagement.clickNextButton();
+ });
+
+ await test.step('skip aliases', async () => {
+ await expect(page.testSubj.locator('stepTitle')).toHaveText('Aliases (optional)');
+ await pageObjects.indexManagement.clickNextButton();
+ });
+
+ await test.step('review and create template', async () => {
+ await expect(page.testSubj.locator('stepTitle')).toHaveText("Review details for 'a-star'");
+ await expect(page.testSubj.locator('summaryTabContent')).toBeVisible();
+ await expect(page.testSubj.locator('indexModeTitle')).toBeVisible();
+ await expect(page.testSubj.locator('indexModeValue')).toHaveText('Standard');
+ await pageObjects.indexManagement.clickNextButton();
+ });
+
+ await test.step('preview template and verify no errors', async () => {
+ await page.testSubj.locator('previewTabBtn').click();
+ const templatePreview = await page.testSubj.locator('simulateTemplatePreview').textContent();
+ expect(templatePreview).not.toContain('error');
+ await page.testSubj.locator('closeDetailsButton').click();
+ });
+ });
+});
diff --git a/x-pack/platform/plugins/shared/index_management/tsconfig.json b/x-pack/platform/plugins/shared/index_management/tsconfig.json
index 9c508f02c85d4..c540c453b58b7 100644
--- a/x-pack/platform/plugins/shared/index_management/tsconfig.json
+++ b/x-pack/platform/plugins/shared/index_management/tsconfig.json
@@ -65,7 +65,8 @@
"@kbn/inference-common",
"@kbn/upgrade-assistant-pkg-common",
"@kbn/failure-store-modal",
- "@kbn/react-query"
+ "@kbn/react-query",
+ "@kbn/scout"
],
"exclude": ["target/**/*"]
}
diff --git a/x-pack/platform/test/functional/apps/index_management/home_page.ts b/x-pack/platform/test/functional/apps/index_management/home_page.ts
deleted file mode 100644
index 3cdcadef65b40..0000000000000
--- a/x-pack/platform/test/functional/apps/index_management/home_page.ts
+++ /dev/null
@@ -1,150 +0,0 @@
-/*
- * 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 type { FtrProviderContext } from '../../ftr_provider_context';
-
-export default ({ getPageObjects, getService }: FtrProviderContext) => {
- const testSubjects = getService('testSubjects');
- const pageObjects = getPageObjects(['common', 'indexManagement', 'header']);
- const log = getService('log');
- const browser = getService('browser');
- const retry = getService('retry');
- const security = getService('security');
- const es = getService('es');
-
- describe('Home page', function () {
- before(async () => {
- await security.testUser.setRoles(['index_management_user']);
- await pageObjects.common.navigateToApp('indexManagement');
- });
-
- it('Loads the app and renders the indices tab by default', async () => {
- await log.debug('Checking for section heading to say Index Management.');
-
- const headingText = await pageObjects.indexManagement.sectionHeadingText();
- expect(headingText).to.be('Index Management');
-
- // Verify url
- const url = await browser.getCurrentUrl();
- expect(url).to.contain(`/indices`);
-
- // Verify content
- const indicesList = await testSubjects.exists('indicesList');
- expect(indicesList).to.be(true);
-
- const reloadIndicesButton = await pageObjects.indexManagement.reloadIndicesButton();
- expect(await reloadIndicesButton.isDisplayed()).to.be(true);
- });
-
- describe('Indices', function () {
- const testIndexName = `index-test-${Math.random()}`;
-
- it('renders the indices tab', async () => {
- // Navigate to the data streams tab
- await pageObjects.indexManagement.changeTabs('indicesTab');
-
- await pageObjects.header.waitUntilLoadingHasFinished();
-
- // Verify url
- const url = await browser.getCurrentUrl();
- expect(url).to.contain(`/indices`);
-
- // Verify content
- await retry.waitFor('Wait until indices table is visible.', async () => {
- return await testSubjects.isDisplayed('indexTable');
- });
- });
-
- it('can create an index', async () => {
- await pageObjects.indexManagement.clickCreateIndexButton();
- await pageObjects.indexManagement.setCreateIndexName(testIndexName);
- await pageObjects.indexManagement.setCreateIndexMode('Lookup');
- await pageObjects.indexManagement.clickCreateIndexSaveButton();
- await pageObjects.indexManagement.expectIndexToExist(testIndexName);
- });
-
- after(async () => {
- try {
- await es.indices.delete({ index: testIndexName });
- } catch (e) {
- log.debug(`Index cleanup failed for ${testIndexName}: ${e.message}`);
- }
- });
- });
-
- describe('Data streams', () => {
- it('renders the data streams tab', async () => {
- // Navigate to the data streams tab
- await pageObjects.indexManagement.changeTabs('data_streamsTab');
-
- await pageObjects.header.waitUntilLoadingHasFinished();
-
- // Verify url
- const url = await browser.getCurrentUrl();
- expect(url).to.contain(`/data_streams`);
-
- // Verify content
- await retry.waitFor('Wait until dataStream Table is visible.', async () => {
- return await testSubjects.isDisplayed('dataStreamTable');
- });
- });
- });
-
- describe('Index templates', () => {
- it('renders the index templates tab', async () => {
- // Navigate to the index templates tab
- await pageObjects.indexManagement.changeTabs('templatesTab');
-
- await pageObjects.header.waitUntilLoadingHasFinished();
-
- // Verify url
- const url = await browser.getCurrentUrl();
- expect(url).to.contain(`/templates`);
-
- // Verify content
- const templateList = await testSubjects.exists('templateList');
- expect(templateList).to.be(true);
- });
- });
-
- describe('Component templates', () => {
- it('renders the component templates tab', async () => {
- // Navigate to the component templates tab
- await pageObjects.indexManagement.changeTabs('component_templatesTab');
-
- await pageObjects.header.waitUntilLoadingHasFinished();
-
- // Verify url
- const url = await browser.getCurrentUrl();
- expect(url).to.contain(`/component_templates`);
-
- // Verify content. Component templates may have been created by other apps, e.g. Ingest Manager,
- // so we don't make any assertion about the presence or absence of component templates.
- const componentTemplateList = await testSubjects.exists('componentTemplateList');
- expect(componentTemplateList).to.be(true);
- });
- });
-
- describe('Enrich policies', () => {
- it('renders the enrich policies tab', async () => {
- // Navigate to the enrich policies tab
- await pageObjects.indexManagement.changeTabs('enrich_policiesTab');
-
- await pageObjects.header.waitUntilLoadingHasFinished();
-
- // Verify url
- const url = await browser.getCurrentUrl();
- expect(url).to.contain(`/enrich_policies`);
-
- // Verify content
- const enrichPoliciesList = await testSubjects.exists('sectionEmpty');
- expect(enrichPoliciesList).to.be(true);
- });
- });
- });
-};
diff --git a/x-pack/platform/test/functional/apps/index_management/index.ts b/x-pack/platform/test/functional/apps/index_management/index.ts
index 20303af196119..3da18702e6e16 100644
--- a/x-pack/platform/test/functional/apps/index_management/index.ts
+++ b/x-pack/platform/test/functional/apps/index_management/index.ts
@@ -10,9 +10,6 @@ import type { FtrProviderContext } from '../../ftr_provider_context';
export default ({ loadTestFile }: FtrProviderContext) => {
describe('Index Management app', function () {
loadTestFile(require.resolve('./feature_controls'));
- loadTestFile(require.resolve('./home_page'));
- loadTestFile(require.resolve('./index_template_wizard'));
- loadTestFile(require.resolve('./index_details_page'));
loadTestFile(require.resolve('./enrich_policies_tab'));
loadTestFile(require.resolve('./create_enrich_policy'));
loadTestFile(require.resolve('./data_streams_tab'));
diff --git a/x-pack/platform/test/functional/apps/index_management/index_details_page.ts b/x-pack/platform/test/functional/apps/index_management/index_details_page.ts
deleted file mode 100644
index fc35a1212c610..0000000000000
--- a/x-pack/platform/test/functional/apps/index_management/index_details_page.ts
+++ /dev/null
@@ -1,31 +0,0 @@
-/*
- * 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 type { FtrProviderContext } from '../../ftr_provider_context';
-
-export default ({ getPageObjects, getService }: FtrProviderContext) => {
- const pageObjects = getPageObjects(['common', 'indexManagement', 'header']);
- const log = getService('log');
- const security = getService('security');
-
- describe('Index details page', function () {
- before(async () => {
- await security.testUser.setRoles(['index_management_user']);
- await pageObjects.common.navigateToApp('indexManagement');
- });
-
- it('Navigates to the index details page from the home page', async () => {
- await log.debug('Navigating to the index details page');
-
- // display hidden indices to have some rows in the indices table
- await pageObjects.indexManagement.toggleHiddenIndices();
- // click the first index in the table and wait for the index details page
- await pageObjects.indexManagement.indexDetailsPage.openIndexDetailsPage(0);
- await pageObjects.indexManagement.indexDetailsPage.expectIndexDetailsPageIsLoaded();
- });
- });
-};
diff --git a/x-pack/platform/test/functional/apps/index_management/index_template_wizard.ts b/x-pack/platform/test/functional/apps/index_management/index_template_wizard.ts
deleted file mode 100644
index 3d5d0b1e8e26f..0000000000000
--- a/x-pack/platform/test/functional/apps/index_management/index_template_wizard.ts
+++ /dev/null
@@ -1,307 +0,0 @@
-/*
- * 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 type { FtrProviderContext } from '../../ftr_provider_context';
-
-export default ({ getPageObjects, getService }: FtrProviderContext) => {
- const testSubjects = getService('testSubjects');
- const pageObjects = getPageObjects(['common', 'indexManagement', 'header']);
- const security = getService('security');
- const comboBox = getService('comboBox');
- const find = getService('find');
- const browser = getService('browser');
- const log = getService('log');
- const es = getService('es');
-
- describe('Index template wizard', function () {
- before(async () => {
- await security.testUser.setRoles(['index_management_user']);
- });
-
- describe('Create', () => {
- before(async () => {
- await pageObjects.indexManagement.navigateToIndexManagementTab('templates');
- // Click Create Template button
- await testSubjects.click('createTemplateButton');
- });
-
- after(async () => {
- try {
- await es.indices.deleteIndexTemplate({ name: 'test-index-template' });
- await pageObjects.indexManagement.navigateToIndexManagementTab('templates');
- } catch (e) {
- log.debug(`Template cleanup failed for test-index-template: ${e.message}`);
- }
- });
-
- it('should set the correct page title', async () => {
- const pageTitle = await testSubjects.exists('pageTitle');
- expect(pageTitle).to.be(true);
-
- const pageTitleText = await testSubjects.getVisibleText('pageTitle');
- expect(pageTitleText).to.be('Create template');
- });
-
- it('renders logistics (step 1)', async () => {
- // Verify step title
- const stepTitle = await testSubjects.getVisibleText('stepTitle');
- expect(stepTitle).to.be('Logistics');
-
- // Fill out required fields
- await testSubjects.setValue('nameField', 'test-index-template');
- await testSubjects.setValue('indexPatternsField', 'test-index-pattern');
-
- // Click Next button
- await pageObjects.indexManagement.clickNextButton();
- });
-
- it('renders component templates (step 2)', async () => {
- // Verify empty prompt
- const emptyPrompt = await testSubjects.exists('emptyPrompt');
- expect(emptyPrompt).to.be(true);
-
- // Click Next button
- await pageObjects.indexManagement.clickNextButton();
- });
-
- it('renders index settings (step 3)', async () => {
- // Verify step title
- const stepTitle = await testSubjects.getVisibleText('stepTitle');
- expect(stepTitle).to.be('Index settings (optional)');
-
- // Verify that index mode callout is displayed
- const indexModeCalloutText = await testSubjects.getVisibleText('indexModeCallout');
- expect(indexModeCalloutText).to.be(
- 'The index.mode setting has been set to Standard within the Logistics step. Any changes to index.mode set on this page will be overwritten by the Logistics selection.'
- );
-
- // Click Next button
- await pageObjects.indexManagement.clickNextButton();
- });
-
- it('renders mappings (step 4)', async () => {
- // Verify step title
- const stepTitle = await testSubjects.getVisibleText('stepTitle');
- expect(stepTitle).to.be('Mappings (optional)');
-
- // Click Next button
- await pageObjects.indexManagement.clickNextButton();
- });
-
- it('renders aliases (step 5)', async () => {
- // Verify step title
- const stepTitle = await testSubjects.getVisibleText('stepTitle');
- expect(stepTitle).to.be('Aliases (optional)');
-
- // Click Next button
- await pageObjects.indexManagement.clickNextButton();
- });
-
- it('renders review template (step 6)', async () => {
- // Verify step title
- const stepTitle = await testSubjects.getVisibleText('stepTitle');
- expect(stepTitle).to.be("Review details for 'test-index-template'");
-
- // Verify that summary exists
- const summaryTabContent = await testSubjects.exists('summaryTabContent');
- expect(summaryTabContent).to.be(true);
-
- // Verify that index mode is set to "Standard"
- expect(await testSubjects.exists('indexModeTitle')).to.be(true);
- expect(await testSubjects.getVisibleText('indexModeValue')).to.be('Standard');
-
- // Click Create template
- await pageObjects.indexManagement.clickNextButton();
- });
- });
-
- // https://github.com/elastic/kibana/pull/195174
- describe('Preview template test', () => {
- before(async () => {
- await pageObjects.indexManagement.navigateToIndexManagementTab('templates');
- });
-
- after(async () => {
- try {
- await es.indices.deleteIndexTemplate({ name: 'a-star' });
- } catch (e) {
- log.debug(`Template cleanup failed for a-star: ${e.message}`);
- }
- });
-
- it('can preview index template that matches a_fake_index_pattern_that_wont_match_any_indices', async () => {
- // Click Create Template button
- await testSubjects.click('createTemplateButton');
- const pageTitleText = await testSubjects.getVisibleText('pageTitle');
- expect(pageTitleText).to.be('Create template');
-
- const stepTitle1 = await testSubjects.getVisibleText('stepTitle');
- expect(stepTitle1).to.be('Logistics');
-
- // Fill out required fields
- await testSubjects.setValue('nameField', 'a-star');
- await testSubjects.setValue('indexPatternsField', 'a*');
- await testSubjects.setValue('priorityField', '1000');
-
- // Click Next button
- await pageObjects.indexManagement.clickNextButton();
-
- // Verify empty prompt
- const emptyPrompt = await testSubjects.exists('emptyPrompt');
- expect(emptyPrompt).to.be(true);
-
- // Click Next button
- await pageObjects.indexManagement.clickNextButton();
-
- // Verify step title
- const stepTitle2 = await testSubjects.getVisibleText('stepTitle');
- expect(stepTitle2).to.be('Index settings (optional)');
-
- // Click Next button
- await pageObjects.indexManagement.clickNextButton();
-
- // Verify step title
- const stepTitle3 = await testSubjects.getVisibleText('stepTitle');
- expect(stepTitle3).to.be('Mappings (optional)');
-
- // Click Next button
- await pageObjects.indexManagement.clickNextButton();
-
- // Verify step title
- const stepTitle4 = await testSubjects.getVisibleText('stepTitle');
- expect(stepTitle4).to.be('Aliases (optional)');
-
- // Click Next button
- await pageObjects.indexManagement.clickNextButton();
-
- // Verify step title
- const stepTitle = await testSubjects.getVisibleText('stepTitle');
- expect(stepTitle).to.be("Review details for 'a-star'");
-
- // Verify that summary exists
- const summaryTabContent = await testSubjects.exists('summaryTabContent');
- expect(summaryTabContent).to.be(true);
-
- // Verify that index mode is set to "Standard"
- expect(await testSubjects.exists('indexModeTitle')).to.be(true);
- expect(await testSubjects.getVisibleText('indexModeValue')).to.be('Standard');
-
- // Click Create template
- await pageObjects.indexManagement.clickNextButton();
-
- // Click preview tab, we know its the last one
- const tabs = await testSubjects.findAll('tab');
- await tabs[tabs.length - 1].click();
- const templatePreview = await testSubjects.getVisibleText('simulateTemplatePreview');
- expect(templatePreview).to.not.contain('error');
-
- await testSubjects.click('closeDetailsButton');
- });
- });
- describe('Mappings step', () => {
- beforeEach(async () => {
- await pageObjects.indexManagement.navigateToIndexManagementTab('templates');
-
- // Click Create Template button
- await testSubjects.click('createTemplateButton');
-
- // Fill out required fields
- await testSubjects.setValue('nameField', 'test-index-template');
- await testSubjects.setValue('indexPatternsField', 'test-index-pattern');
-
- // Go to Mappings step
- await testSubjects.click('formWizardStep-3');
- expect(await testSubjects.getVisibleText('stepTitle')).to.be('Mappings (optional)');
- });
-
- afterEach(async () => {
- try {
- await es.indices.deleteIndexTemplate({ name: 'test-index-template' });
- } catch (e) {
- log.debug(`Template cleanup failed in mappings test: ${e.message}`);
- }
- });
-
- // Test for catching the bug reported in https://github.com/elastic/kibana/issues/156202
- it("clearing up the Numeric subtype dropdown doesn't break the page", async () => {
- // Add a mapping field
- await testSubjects.click('addFieldButton');
-
- // Select Numeric type
- await testSubjects.click('fieldType');
- await comboBox.set('fieldType', 'Numeric');
-
- // Clear up subtype dropdown
- await testSubjects.click('fieldSubType');
- const input = await find.activeElement();
- await input.pressKeys(browser.keys.BACK_SPACE);
-
- // Verify that elements are still visible
- expect(await testSubjects.exists('addFieldButton')).to.be(true);
- expect(await testSubjects.exists('fieldType')).to.be(true);
- expect(await testSubjects.exists('fieldSubType')).to.be(true);
- expect(await testSubjects.exists('nextButton')).to.be(true);
- });
-
- // Test for catching the bug reported in https://github.com/elastic/kibana/issues/156202
- it("clearing up the Range subtype dropdown doesn't break the page", async () => {
- // Add a mapping field
- await testSubjects.click('addFieldButton');
-
- // Select Range type
- await testSubjects.click('fieldType');
- await comboBox.set('fieldType', 'Range');
-
- // Clear up subtype dropdown
- await testSubjects.click('fieldSubType');
- const input = await find.activeElement();
- await input.pressKeys(browser.keys.BACK_SPACE);
-
- // Verify that elements are still visible
- expect(await testSubjects.exists('addFieldButton')).to.be(true);
- expect(await testSubjects.exists('fieldType')).to.be(true);
- expect(await testSubjects.exists('fieldSubType')).to.be(true);
- expect(await testSubjects.exists('nextButton')).to.be(true);
- });
-
- it("advanced options tab doesn't add default values to request by default", async () => {
- await pageObjects.indexManagement.changeMappingsEditorTab('advancedOptions');
- await testSubjects.click('previewIndexTemplate');
- const templatePreview = await testSubjects.getVisibleText('simulateTemplatePreview');
-
- await log.debug(`Template preview text: ${templatePreview}`);
-
- // All advanced options should not be part of the request
- expect(templatePreview).to.not.contain('"dynamic"');
- expect(templatePreview).to.not.contain('"subobjects"');
- expect(templatePreview).to.not.contain('"dynamic_date_formats"');
- expect(templatePreview).to.not.contain('"date_detection"');
- expect(templatePreview).to.not.contain('"numeric_detection"');
- });
-
- it('advanced options tab adds the set values to the request', async () => {
- await pageObjects.indexManagement.changeMappingsEditorTab('advancedOptions');
-
- // Toggle the subobjects field to false
- await testSubjects.click('subobjectsToggle');
-
- await testSubjects.click('previewIndexTemplate');
- const templatePreview = await testSubjects.getVisibleText('simulateTemplatePreview');
-
- await log.debug(`Template preview text: ${templatePreview}`);
-
- // Only the subobjects option should be part of the request
- expect(templatePreview).to.contain('"subobjects": false');
- expect(templatePreview).to.not.contain('"dynamic"');
- expect(templatePreview).to.not.contain('"dynamic_date_formats"');
- expect(templatePreview).to.not.contain('"date_detection"');
- expect(templatePreview).to.not.contain('"numeric_detection"');
- });
- });
- });
-};
diff --git a/x-pack/platform/test/functional/apps/index_management/index_templates_tab/index_template_tab.ts b/x-pack/platform/test/functional/apps/index_management/index_templates_tab/index_template_tab.ts
index 3fa29ca06911c..3bd4a7ec2da3f 100644
--- a/x-pack/platform/test/functional/apps/index_management/index_templates_tab/index_template_tab.ts
+++ b/x-pack/platform/test/functional/apps/index_management/index_templates_tab/index_template_tab.ts
@@ -140,8 +140,7 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => {
// Navigate to Mappings
await testSubjects.click('formWizardStep-3');
await pageObjects.header.waitUntilLoadingHasFinished();
- const mappingTabs = await testSubjects.findAll('formTab');
- await mappingTabs[3].click();
+ await testSubjects.click('advancedOptionsTab');
// Modify timestamp format
await testSubjects.click('comboBoxClearButton');
@@ -159,10 +158,8 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => {
await pageObjects.indexManagement.clickNextButton();
await pageObjects.header.waitUntilLoadingHasFinished();
- const flyoutTabs = await testSubjects.findAll('tab');
-
// Verify Index Settings
- await flyoutTabs[1].click();
+ await testSubjects.click('settingsTabBtn');
await pageObjects.header.waitUntilLoadingHasFinished();
expect(await testSubjects.exists('settingsTabContent')).to.be(true);
const settingsTabContent = await testSubjects.getVisibleText('settingsTabContent');
@@ -180,7 +177,7 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => {
});
// Verify Mappings
- await flyoutTabs[2].click();
+ await testSubjects.click('mappingsTabBtn');
await pageObjects.header.waitUntilLoadingHasFinished();
expect(await testSubjects.exists('mappingsTabContent')).to.be(true);
const mappingsTabContent = await testSubjects.getVisibleText('mappingsTabContent');
@@ -197,8 +194,7 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => {
// Navigate to Mappings
await testSubjects.click('formWizardStep-3');
await pageObjects.header.waitUntilLoadingHasFinished();
- const mappingTabs = await testSubjects.findAll('formTab');
- await mappingTabs[3].click();
+ await (await testSubjects.find('advancedOptionsTab')).click();
// Modify source
await testSubjects.click('sourceValueField');
diff --git a/x-pack/platform/test/serverless/functional/test_suites/management/index_management/index_templates.ts b/x-pack/platform/test/serverless/functional/test_suites/management/index_management/index_templates.ts
index 6dbc821d50f10..bba604253755e 100644
--- a/x-pack/platform/test/serverless/functional/test_suites/management/index_management/index_templates.ts
+++ b/x-pack/platform/test/serverless/functional/test_suites/management/index_management/index_templates.ts
@@ -176,8 +176,7 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => {
// Navigate to Mappings
await testSubjects.click('formWizardStep-3');
await pageObjects.header.waitUntilLoadingHasFinished();
- const mappingTabs = await testSubjects.findAll('formTab');
- await mappingTabs[3].click();
+ await testSubjects.click('advancedOptionsTab');
// Modify timestamp format
await testSubjects.click('comboBoxClearButton');
@@ -195,10 +194,8 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => {
await pageObjects.indexManagement.clickNextButton();
await pageObjects.header.waitUntilLoadingHasFinished();
- const flyoutTabs = await testSubjects.findAll('tab');
-
// Verify Index Settings
- await flyoutTabs[1].click();
+ await testSubjects.click('settingsTabBtn');
await pageObjects.header.waitUntilLoadingHasFinished();
expect(await testSubjects.exists('settingsTabContent')).to.be(true);
const settingsTabContent = await testSubjects.getVisibleText('settingsTabContent');
@@ -216,7 +213,7 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => {
});
// Verify Mappings
- await flyoutTabs[2].click();
+ await testSubjects.click('mappingsTabBtn');
await pageObjects.header.waitUntilLoadingHasFinished();
expect(await testSubjects.exists('mappingsTabContent')).to.be(true);
const mappingsTabContent = await testSubjects.getVisibleText('mappingsTabContent');