diff --git a/src/platform/packages/shared/kbn-scout/src/playwright/eui_components/combo_box.ts b/src/platform/packages/shared/kbn-scout/src/playwright/eui_components/combo_box.ts index b562890dc98e7..f616ce991a8ae 100644 --- a/src/platform/packages/shared/kbn-scout/src/playwright/eui_components/combo_box.ts +++ b/src/platform/packages/shared/kbn-scout/src/playwright/eui_components/combo_box.ts @@ -13,6 +13,11 @@ import { expect } from '@playwright/test'; import type { ScoutPage } from '../fixtures/scope/test/scout_page'; import { resolveSelector, type SelectorInput } from '../utils'; +export interface ComboBoxInputOptions { + /** When true, uses fill() instead of pressSequentially() for faster input without keystroke simulation. */ + useFill?: boolean; +} + // https://eui.elastic.co/docs/components/forms/selection/combo-box/ export class EuiComboBoxWrapper { private readonly page: ScoutPage; @@ -65,17 +70,21 @@ export class EuiComboBoxWrapper { await this.page.keyboard.press('Escape'); } - private async typeValueInSearch(value: string) { - await this.comboBoxSearchInput.pressSequentially(value, { delay: 50 }); + private async typeValueInSearch(value: string, options?: ComboBoxInputOptions) { + if (options?.useFill) { + await this.comboBoxSearchInput.fill(value); + } else { + await this.comboBoxSearchInput.pressSequentially(value, { delay: 50 }); + } } - async selectMultiOption(value: string) { + async selectMultiOption(value: string, options?: ComboBoxInputOptions) { await this.checkIfAlreadySelected(value); // put cursor in the comboBox input field await this.comboBoxMainInput.click(); // type the value with a delay to allow for async option loading - await this.typeValueInSearch(value); + await this.typeValueInSearch(value, options); // select the option that matches the value const trimmedValue = value.trim(); await this.page.locator(`.euiFilterSelectItem[title="${trimmedValue}"]`).click(); @@ -85,7 +94,7 @@ export class EuiComboBoxWrapper { await this.verifySelectionAndClose(value); } - async selectMultiOptions(values: string[]) { + async selectMultiOptions(values: string[], options?: ComboBoxInputOptions) { const selectedOptions = await this.getSelectedMultiOptions(); // Check if any values are already selected before starting UI interactions @@ -97,7 +106,7 @@ export class EuiComboBoxWrapper { await this.comboBoxMainInput.click(); for (const value of values) { - await this.typeValueInSearch(value); + await this.typeValueInSearch(value, options); const trimmedValue = value.trim(); await this.page.locator(`.euiFilterSelectItem[title="${trimmedValue}"]`).click(); await this.waitForBadgeToBe(value, 'visible'); @@ -108,11 +117,11 @@ export class EuiComboBoxWrapper { await this.page.keyboard.press('Escape'); } - async setCustomMultiOption(value: string) { + async setCustomMultiOption(value: string, options?: ComboBoxInputOptions) { await this.checkIfAlreadySelected(value); await this.comboBoxMainInput.click(); - await this.typeValueInSearch(value); + await this.typeValueInSearch(value, options); await this.page.keyboard.press('Enter'); await this.waitForBadgeToBe(value, 'visible'); @@ -146,7 +155,6 @@ export class EuiComboBoxWrapper { expect(await this.getSelectedMultiOptions()).not.toContain(value); } - // Select a single option in the comboBox async selectSingleOption( value: string, options: { @@ -154,11 +162,11 @@ export class EuiComboBoxWrapper { optionRoleName?: string; /** Use for combos backed by slow suggestion APIs so other suites keep default waits. */ optionVisibilityTimeoutMs?: number; - } = {} + } & ComboBoxInputOptions = {} ) { await this.clear(); await this.comboBoxMainInput.click(); - await this.typeValueInSearch(value); + await this.typeValueInSearch(value, options); // Prefer a specific test subj when option text is ambiguous. const trimmedValue = value.trim(); const optionLocator = options.optionTestSubj @@ -182,11 +190,11 @@ export class EuiComboBoxWrapper { options: { /** Use when confirming selection may lag after Enter (slow suggestions / CI). */ settleTimeoutMs?: number; - } = {} + } & ComboBoxInputOptions = {} ) { await this.clear(); await this.comboBoxMainInput.click(); - await this.typeValueInSearch(value); + await this.typeValueInSearch(value, options); await this.page.keyboard.press('Enter'); await expect .poll(async () => await this.getSelectedValue(), { diff --git a/src/platform/packages/shared/kbn-scout/src/playwright/eui_components/index.ts b/src/platform/packages/shared/kbn-scout/src/playwright/eui_components/index.ts index 96752e100685c..7450d43086b34 100644 --- a/src/platform/packages/shared/kbn-scout/src/playwright/eui_components/index.ts +++ b/src/platform/packages/shared/kbn-scout/src/playwright/eui_components/index.ts @@ -8,6 +8,7 @@ */ import { EuiComboBoxWrapper } from './combo_box'; +import type { ComboBoxInputOptions } from './combo_box'; import { EuiSelectableWrapper } from './selectable'; import { EuiCheckBoxWrapper } from './check_box'; import { EuiDataGridWrapper } from './data_grid'; @@ -16,6 +17,7 @@ import { EuiFieldTextWrapper } from './field_text'; import { EuiCodeBlockWrapper } from './code_block'; import { EuiSuperSelectWrapper } from './super_select'; +export type { ComboBoxInputOptions }; export { EuiComboBoxWrapper, EuiSelectableWrapper, diff --git a/src/platform/packages/shared/kbn-scout/src/playwright/page_objects/dashboard_app.ts b/src/platform/packages/shared/kbn-scout/src/playwright/page_objects/dashboard_app.ts index 69370cdef38b2..514b8ccd7c4dd 100644 --- a/src/platform/packages/shared/kbn-scout/src/playwright/page_objects/dashboard_app.ts +++ b/src/platform/packages/shared/kbn-scout/src/playwright/page_objects/dashboard_app.ts @@ -21,6 +21,10 @@ type CommonlyUsedTimeRange = | 'Last_90 days' | 'Last_1 year'; +interface TimeoutOptions { + timeout?: number; +} + export class DashboardApp { private readonly renderable: RenderablePage; private readonly toasts: Toasts; @@ -133,9 +137,9 @@ export class DashboardApp { } /** Navigates to the new dashboard creation page and waits for the editor toolbar to load. */ - async openNewDashboard() { + async openNewDashboard(options?: TimeoutOptions) { await this.page.gotoApp('dashboards', { hash: '/create' }); - await expect(this.addTopNavButton).toBeVisible({ timeout: 20_000 }); + await expect(this.addTopNavButton).toBeVisible({ timeout: options?.timeout ?? 20_000 }); } private getSettingsFlyout() { @@ -264,12 +268,12 @@ export class DashboardApp { * Opens the "Add from library" flyout. * Low-level building block used by addEmbeddable(). */ - private async openLibraryFlyout() { + private async openLibraryFlyout(options?: TimeoutOptions) { await this.addTopNavButton.click(); await this.page.testSubj.click('addToDashboardTab-library'); await expect(this.savedObjectsFinderTable).toBeVisible(); await expect(this.savedObjectFinderLoadingIndicator).toBeHidden({ - timeout: 30_000, + timeout: options?.timeout ?? 30_000, }); } @@ -287,8 +291,13 @@ export class DashboardApp { * * @param embeddableName - Name with dashes (e.g., 'Rendering-Test:-saved-search') * @param embeddableType - Optional type filter (e.g., 'search', 'Visualization') + * @param options - Optional timeout overrides */ - private async filterEmbeddableNames(embeddableName: string, embeddableType?: string) { + private async filterEmbeddableNames( + embeddableName: string, + embeddableType?: string, + options?: TimeoutOptions + ) { // Build search query using type filter and quoted name. // type:(search) "Rendering Test:-saved-search" (only first dash replaced with space) const typePrefix = embeddableType ? `type:(${embeddableType}) ` : ''; @@ -301,7 +310,7 @@ export class DashboardApp { // Wait for search results to load await expect(this.savedObjectFinderLoadingIndicator).toBeHidden({ - timeout: 30_000, + timeout: options?.timeout ?? 30_000, }); } @@ -310,10 +319,11 @@ export class DashboardApp { * * @param embeddableName - Name with dashes (e.g., 'Rendering-Test:-saved-search') * @param embeddableType - Optional type filter (e.g., 'search', 'Visualization') + * @param options - Optional timeout overrides */ - async addEmbeddable(embeddableName: string, embeddableType?: string) { - await this.openLibraryFlyout(); - await this.filterEmbeddableNames(embeddableName, embeddableType); + async addEmbeddable(embeddableName: string, embeddableType?: string, options?: TimeoutOptions) { + await this.openLibraryFlyout(options); + await this.filterEmbeddableNames(embeddableName, embeddableType, options); // Click on the saved object title const titleSelector = `savedObjectTitle${embeddableName.split(' ').join('-')}`; @@ -335,9 +345,10 @@ export class DashboardApp { * Wrapper around addEmbeddable() with type='search'. * * @param searchName - Name with dashes (e.g., 'Rendering-Test:-saved-search') + * @param options - Optional timeout overrides */ - async addSavedSearch(searchName: string) { - return this.addEmbeddable(searchName, 'search'); + async addSavedSearch(searchName: string, options?: TimeoutOptions) { + return this.addEmbeddable(searchName, 'search', options); } /** @@ -345,9 +356,10 @@ export class DashboardApp { * Wrapper around addEmbeddable() with type='lens'. * * @param lensName - Name of the Lens saved object + * @param options - Optional timeout overrides */ - async addLens(lensName: string) { - return this.addEmbeddable(lensName, 'lens'); + async addLens(lensName: string, options?: TimeoutOptions) { + return this.addEmbeddable(lensName, 'lens', options); } /** diff --git a/x-pack/solutions/observability/plugins/apm/test/scout/ui/parallel_tests/service_map/service_map_embeddable.spec.ts b/x-pack/solutions/observability/plugins/apm/test/scout/ui/parallel_tests/service_map/service_map_embeddable.spec.ts index 248f5c0d9cc65..5c4c1011939a8 100644 --- a/x-pack/solutions/observability/plugins/apm/test/scout/ui/parallel_tests/service_map/service_map_embeddable.spec.ts +++ b/x-pack/solutions/observability/plugins/apm/test/scout/ui/parallel_tests/service_map/service_map_embeddable.spec.ts @@ -14,8 +14,7 @@ const APM_DASHBOARD_DATA_VIEW_TITLE = 'traces-apm*,logs-apm*,metrics-apm*'; const { SERVICE_MAP_TEST_SERVICE, SERVICE_MAP_TEST_ENVIRONMENT_STAGING } = testData; -// Failing: See https://github.com/elastic/kibana/issues/265639 -test.describe.skip( +test.describe( 'Service map embeddable', { tag: [...tags.stateful.classic, ...tags.serverless.observability.complete] }, () => { @@ -47,14 +46,12 @@ test.describe.skip( pageObjects, }) => { await test.step('open a new dashboard', async () => { - await pageObjects.dashboard.openNewDashboard(); + await pageObjects.dashboard.openNewDashboard({ timeout: EXTENDED_TIMEOUT }); }); await test.step('set time range to last 1 hour to ensure test data is visible', async () => { await pageObjects.datePicker.setCommonlyUsedTime('Last_1_hour'); - await page - .getByTestId('dateRangePickerControlButton') - .waitFor({ timeout: EXTENDED_TIMEOUT }); + await expect(page.getByTestId('dateRangePickerControlButton')).toContainText('Last 1 hour'); await page.getByTestId('dateRangePickerControlButton').blur(); }); @@ -90,7 +87,7 @@ test.describe.skip( page, 'apmServiceMapEditorServiceNameComboBox' ); - await serviceNameComboBox.selectSingleOption(SERVICE_MAP_TEST_SERVICE); + await serviceNameComboBox.selectSingleOption(SERVICE_MAP_TEST_SERVICE, { useFill: true }); // Select environment from dropdown (has a default value so manually type and select) const environmentInput = page.testSubj @@ -148,6 +145,9 @@ test.describe.skip( const popoverTitle = page.testSubj.locator('serviceMapPopoverTitle'); await expect(popoverTitle).toHaveText(SERVICE_MAP_TEST_SERVICE); + + await page.keyboard.press('Escape'); + await expect(popoverTitle).toBeHidden(); }); await test.step('maximize the Service map panel', async () => {