diff --git a/.buildkite/scripts/steps/functional/scout_ui_tests.sh b/.buildkite/scripts/steps/functional/scout_ui_tests.sh index 9eb31b6b0e6b1..57e463ecf9e74 100755 --- a/.buildkite/scripts/steps/functional/scout_ui_tests.sh +++ b/.buildkite/scripts/steps/functional/scout_ui_tests.sh @@ -24,10 +24,11 @@ run_tests() { EXIT_CODE=0 -# Discovery Enhanced +# Discovery Enhanced && Maps for run_mode in "--stateful" "--serverless=es" "--serverless=oblt" "--serverless=security"; do run_tests "Discovery Enhanced: Parallel Workers" "x-pack/platform/plugins/private/discover_enhanced/ui_tests/parallel.playwright.config.ts" "$run_mode" run_tests "Discovery Enhanced" "x-pack/platform/plugins/private/discover_enhanced/ui_tests/playwright.config.ts" "$run_mode" + run_tests "Maps" "x-pack/platform/plugins/shared/maps/ui_tests/playwright.config.ts" "$run_mode" done # Observability Onboarding @@ -36,4 +37,4 @@ for run_mode in "--stateful" "--serverless=oblt"; do done -exit $EXIT_CODE +exit $EXIT_CODE \ No newline at end of file diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 52d60d49a04d1..1b5cd84df13e4 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -1588,6 +1588,7 @@ packages/kbn-monaco/src/esql @elastic/kibana-esql /.eslintignore @elastic/kibana-operations # QA - Appex QA +/x-pack/platform/plugins/shared/maps/ui_tests @elastic/appex-qa # temporarily /x-pack/platform/plugins/private/discover_enhanced/ui_tests/ @elastic/appex-qa # temporarily /x-pack/test/functional/fixtures/package_registry_config.yml @elastic/appex-qa # No usages found /x-pack/test/functional/fixtures/kbn_archiver/packaging.json @elastic/appex-qa # No usages found diff --git a/packages/kbn-scout/src/playwright/config/create_config.test.ts b/packages/kbn-scout/src/playwright/config/create_config.test.ts index 3ffb163ed172f..1b2c53fd99bc7 100644 --- a/packages/kbn-scout/src/playwright/config/create_config.test.ts +++ b/packages/kbn-scout/src/playwright/config/create_config.test.ts @@ -41,6 +41,7 @@ describe('createPlaywrightConfig', () => { serversConfigDir: SCOUT_SERVERS_ROOT, [VALID_CONFIG_MARKER]: true, screenshot: 'only-on-failure', + testIdAttribute: 'data-test-subj', trace: 'on-first-retry', }); expect(config.globalSetup).toBeUndefined(); diff --git a/packages/kbn-scout/src/playwright/config/create_config.ts b/packages/kbn-scout/src/playwright/config/create_config.ts index 1a2d2154fadb1..4518f58a72288 100644 --- a/packages/kbn-scout/src/playwright/config/create_config.ts +++ b/packages/kbn-scout/src/playwright/config/create_config.ts @@ -39,6 +39,7 @@ export function createPlaywrightConfig(options: ScoutPlaywrightOptions): Playwri ], /* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */ use: { + testIdAttribute: 'data-test-subj', serversConfigDir: SCOUT_SERVERS_ROOT, [VALID_CONFIG_MARKER]: true, /* Base URL to use in actions like `await page.goto('/')`. */ diff --git a/packages/kbn-scout/src/playwright/page_objects/index.ts b/packages/kbn-scout/src/playwright/page_objects/index.ts index 5e1fbae47e831..59e7f19c96790 100644 --- a/packages/kbn-scout/src/playwright/page_objects/index.ts +++ b/packages/kbn-scout/src/playwright/page_objects/index.ts @@ -12,6 +12,8 @@ import { DashboardApp } from './dashboard_app'; import { DatePicker } from './date_picker'; import { DiscoverApp } from './discover_app'; import { FilterBar } from './fiter_bar'; +import { MapsPage } from './maps_page'; +import { RenderablePage } from './renderable_page'; import { createLazyPageObject } from './utils'; export interface PageObjects { @@ -19,6 +21,8 @@ export interface PageObjects { discover: DiscoverApp; dashboard: DashboardApp; filterBar: FilterBar; + maps: MapsPage; + renderable: RenderablePage; } /** @@ -33,6 +37,8 @@ export function createCorePageObjects(page: ScoutPage): PageObjects { dashboard: createLazyPageObject(DashboardApp, page), discover: createLazyPageObject(DiscoverApp, page), filterBar: createLazyPageObject(FilterBar, page), + maps: createLazyPageObject(MapsPage, page), + renderable: createLazyPageObject(RenderablePage, page), // Add new page objects here }; } diff --git a/packages/kbn-scout/src/playwright/page_objects/maps_page.ts b/packages/kbn-scout/src/playwright/page_objects/maps_page.ts new file mode 100644 index 0000000000000..bff713088b45c --- /dev/null +++ b/packages/kbn-scout/src/playwright/page_objects/maps_page.ts @@ -0,0 +1,18 @@ +/* + * 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", the "GNU Affero General Public License v3.0 only", and the "Server Side + * Public License v 1"; you may not use this file except in compliance with, at + * your election, the "Elastic License 2.0", the "GNU Affero General Public + * License v3.0 only", or the "Server Side Public License, v 1". + */ + +import { ScoutPage } from '..'; + +export class MapsPage { + constructor(private readonly page: ScoutPage) {} + + async gotoNewMap() { + await this.page.gotoApp('maps/map'); + } +} diff --git a/packages/kbn-scout/src/playwright/page_objects/renderable_page.ts b/packages/kbn-scout/src/playwright/page_objects/renderable_page.ts new file mode 100644 index 0000000000000..b7f85a54d60fe --- /dev/null +++ b/packages/kbn-scout/src/playwright/page_objects/renderable_page.ts @@ -0,0 +1,43 @@ +/* + * 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", the "GNU Affero General Public License v3.0 only", and the "Server Side + * Public License v 1"; you may not use this file except in compliance with, at + * your election, the "Elastic License 2.0", the "GNU Affero General Public + * License v3.0 only", or the "Server Side Public License, v 1". + */ + +import { ScoutPage, expect } from '..'; + +export class RenderablePage { + constructor(private readonly page: ScoutPage) {} + + async waitForRender(count: number = 1): Promise { + await expect(async () => await renderWait(count, this.page)).toPass({ + timeout: 10_000, + }); + } +} + +const RENDER_COMPLETE_SELECTOR = '[data-render-complete="true"]'; +const RENDER_COMPLETE_PENDING_SELECTOR = '[data-render-complete="false"]'; +const DATA_LOADING_SELECTOR = '[data-loading]'; + +async function renderWait(count: number, page: ScoutPage) { + const renderCompleteLocator = page.locator(RENDER_COMPLETE_SELECTOR); + const renderPendingDataTitleLocator = page + .locator(RENDER_COMPLETE_PENDING_SELECTOR) + .and(page.locator('data-title')); + const loadingLocator = page.locator(DATA_LOADING_SELECTOR); + + await renderCompleteLocator.waitFor({ timeout: 1000 }); + const completedElementsCount = await renderCompleteLocator.count(); + + if (completedElementsCount < count) + throw new Error( + `${completedElementsCount} elements completed rendering, still waiting on a total of ${count} - ${await renderPendingDataTitleLocator.all()}` + ); + + const loadingCount = await loadingLocator.count(); + if (loadingCount > 0) throw new Error(`${loadingCount} elements still loading contents`); +} diff --git a/packages/kbn-scout/src/playwright/page_objects/utils/index.ts b/packages/kbn-scout/src/playwright/page_objects/utils/index.ts index 1f50901010303..419c1a87c8a1a 100644 --- a/packages/kbn-scout/src/playwright/page_objects/utils/index.ts +++ b/packages/kbn-scout/src/playwright/page_objects/utils/index.ts @@ -15,14 +15,14 @@ import { ScoutPage } from '../../fixtures'; * in certain test scenarios. * * @param PageObjectClass - The page object class to be instantiated lazily. - * @param scoutPage - ScoutPage instance, that extendes the Playwright `page` fixture and passed to the page object class constructor. + * @param scoutPage - ScoutPage instance, that extends the Playwright `page` fixture and passed to the page object class constructor. * @param constructorArgs - Additional arguments to be passed to the page object class constructor. * @returns A proxy object that behaves like an instance of the page object class, instantiating it on demand. */ -export function createLazyPageObject( - PageObjectClass: new (page: ScoutPage, ...args: any[]) => T, +export function createLazyPageObject( + PageObjectClass: new (page: ScoutPage, ...args: Args) => T, scoutPage: ScoutPage, - ...constructorArgs: any[] + ...constructorArgs: Args ): T { let instance: T | null = null; return new Proxy({} as T, { diff --git a/packages/kbn-scout/tsconfig.json b/packages/kbn-scout/tsconfig.json index 9a8fa16477ec2..4be0277f18930 100644 --- a/packages/kbn-scout/tsconfig.json +++ b/packages/kbn-scout/tsconfig.json @@ -27,6 +27,6 @@ "@kbn/mock-idp-utils", "@kbn/test-subj-selector", "@kbn/scout-info", - "@kbn/scout-reporting" + "@kbn/scout-reporting", ] } diff --git a/x-pack/platform/plugins/shared/maps/tsconfig.json b/x-pack/platform/plugins/shared/maps/tsconfig.json index ee6f254b918ed..f93b8b2489d57 100644 --- a/x-pack/platform/plugins/shared/maps/tsconfig.json +++ b/x-pack/platform/plugins/shared/maps/tsconfig.json @@ -9,7 +9,8 @@ "public/**/*", "server/**/*", "server/config.ts", - "../../../../../typings/**/*" + "../../../../../typings/**/*", + "ui_tests/**/*", ], "kbn_references": [ "@kbn/core", @@ -90,7 +91,8 @@ "@kbn/embeddable-enhanced-plugin", "@kbn/presentation-containers", "@kbn/field-utils", - "@kbn/react-hooks" + "@kbn/react-hooks", + "@kbn/scout", ], "exclude": [ "target/**/*", diff --git a/x-pack/platform/plugins/shared/maps/ui_tests/README.md b/x-pack/platform/plugins/shared/maps/ui_tests/README.md new file mode 100644 index 0000000000000..f8bd51d40abf3 --- /dev/null +++ b/x-pack/platform/plugins/shared/maps/ui_tests/README.md @@ -0,0 +1,22 @@ +## How to run tests + +You can drop the following in your terminal. + +```bash +run_tests() { + local suit_name=$1 + local config_path=$2 + local run_mode=$3 + + echo "--- $suit_name ($run_mode) UI Tests" + if ! node scripts/scout run-tests "$run_mode" --config "$config_path"; then + echo "$suit_name: failed" + else + echo "$suit_name: passed" + fi +} + +for run_mode in "--stateful" "--serverless=es" "--serverless=oblt" "--serverless=security"; do + run_tests "Maps" "x-pack/platform/plugins/shared/maps/ui_tests/playwright.config.ts" "$run_mode" +done +``` \ No newline at end of file diff --git a/x-pack/platform/plugins/shared/maps/ui_tests/fixtures/constants.ts b/x-pack/platform/plugins/shared/maps/ui_tests/fixtures/constants.ts new file mode 100644 index 0000000000000..bba25cc0fb560 --- /dev/null +++ b/x-pack/platform/plugins/shared/maps/ui_tests/fixtures/constants.ts @@ -0,0 +1,11 @@ +/* + * 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. + */ + +export const VISIBLE_CHROME = 'kbnAppWrapper visibleChrome'; +export const HIDDEN_CHROME = 'kbnAppWrapper hiddenChrome'; +export const FULL_SCREEN_MODE = 'mapsFullScreenMode'; +export const EXIT_FULL_SCREEN = 'exitFullScreenModeButton'; diff --git a/x-pack/platform/plugins/shared/maps/ui_tests/playwright.config.ts b/x-pack/platform/plugins/shared/maps/ui_tests/playwright.config.ts new file mode 100644 index 0000000000000..34b370396b67e --- /dev/null +++ b/x-pack/platform/plugins/shared/maps/ui_tests/playwright.config.ts @@ -0,0 +1,13 @@ +/* + * 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'; + +// eslint-disable-next-line import/no-default-export +export default createPlaywrightConfig({ + testDir: './tests', +}); diff --git a/x-pack/platform/plugins/shared/maps/ui_tests/tests/full_screen_mode.spec.ts b/x-pack/platform/plugins/shared/maps/ui_tests/tests/full_screen_mode.spec.ts new file mode 100644 index 0000000000000..861baafc7e8c3 --- /dev/null +++ b/x-pack/platform/plugins/shared/maps/ui_tests/tests/full_screen_mode.spec.ts @@ -0,0 +1,54 @@ +/* + * 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, tags, test } from '@kbn/scout'; +import { + VISIBLE_CHROME, + HIDDEN_CHROME, + FULL_SCREEN_MODE, + EXIT_FULL_SCREEN, +} from '../fixtures/constants'; + +test.describe( + 'Maps', + { + tag: tags.DEPLOYMENT_AGNOSTIC, + }, + () => { + test.beforeEach(async ({ browserAuth, pageObjects }) => { + await browserAuth.loginAsViewer(); + await pageObjects.maps.gotoNewMap(); + await pageObjects.renderable.waitForRender(); + }); + + test('Full screen mode', async ({ page }) => { + const fullScreenBtn = page.getByTestId(FULL_SCREEN_MODE); + const exitFullScreenBtn = page.getByTestId(EXIT_FULL_SCREEN); + const visibleChrome = page.getByTestId(VISIBLE_CHROME); + const hiddenChrome = page.getByTestId(HIDDEN_CHROME); + const baseMapBtn = page.getByRole('button', { name: 'Basemap' }); + + await expect(fullScreenBtn).toBeVisible(); + await expect(exitFullScreenBtn).not.toBeVisible(); + await expect(visibleChrome).toBeVisible(); + await expect(hiddenChrome).not.toBeVisible(); + await expect(baseMapBtn).toBeVisible(); + + await fullScreenBtn.click(); + + await expect(fullScreenBtn).not.toBeVisible(); + await expect(exitFullScreenBtn).toBeVisible(); + await expect(visibleChrome).not.toBeVisible(); + await expect(hiddenChrome).toBeVisible(); + await expect(baseMapBtn).toBeVisible(); + + await exitFullScreenBtn.click(); + + await expect(fullScreenBtn).toBeVisible(); + }); + } +);