diff --git a/.buildkite/scout_ci_config.yml b/.buildkite/scout_ci_config.yml index 745d4bdf395a5..f5321446eeb2b 100644 --- a/.buildkite/scout_ci_config.yml +++ b/.buildkite/scout_ci_config.yml @@ -22,6 +22,7 @@ plugins: - transform - fleet - entity_store + - lens disabled: packages: 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 ea149124cf428..c9f2afd96b0a5 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 @@ -117,4 +117,22 @@ export class DashboardApp { throw new Error(`Timeout waiting for ${expectedCount} elements matching ${options.selector}`); } + + async switchToEditMode() { + const isInEditMode = await this.page.testSubj.isVisible('dashboardViewOnlyMode'); + if (!isInEditMode) { + await this.page.testSubj.click('dashboardEditMode'); + await this.page.testSubj.waitForSelector('embeddablePanelDragHandle', { state: 'visible' }); + } + } + + async openInlineEditor(id: string) { + // Hover over the panel to show action buttons + const embeddableSelector = `[data-test-embeddable-id="${id}"]`; + await this.page.locator(embeddableSelector).hover(); + + // Wait for the edit button to appear and click it + const editVisualizationConfigurationSelector = `[data-test-subj="hover-actions-${id}"] [data-test-subj="embeddablePanelAction-editPanel"]`; + await this.page.locator(editVisualizationConfigurationSelector).click(); + } } diff --git a/src/platform/packages/shared/kbn-scout/src/playwright/page_objects/index.ts b/src/platform/packages/shared/kbn-scout/src/playwright/page_objects/index.ts index 21434371d9e79..db0d7375f46e1 100644 --- a/src/platform/packages/shared/kbn-scout/src/playwright/page_objects/index.ts +++ b/src/platform/packages/shared/kbn-scout/src/playwright/page_objects/index.ts @@ -20,6 +20,7 @@ import { RenderablePage } from './renderable_page'; import { Toasts } from './toasts'; import { createLazyPageObject } from './utils'; import { Inspector } from './inspector'; +import { LensApp } from './lens_app'; export interface PageObjectsFixtures { page: ScoutPage; @@ -37,6 +38,7 @@ export interface PageObjects { collapsibleNav: CollapsibleNav; toasts: Toasts; inspector: Inspector; + lens: LensApp; } /** @@ -56,6 +58,7 @@ export function createCorePageObjects(fixtures: PageObjectsFixtures): PageObject collapsibleNav: createLazyPageObject(CollapsibleNav, fixtures.page, fixtures.config), toasts: createLazyPageObject(Toasts, fixtures.page), inspector: createLazyPageObject(Inspector, fixtures.page), + lens: createLazyPageObject(LensApp, fixtures.page), // Add new page objects here }; } diff --git a/src/platform/packages/shared/kbn-scout/src/playwright/page_objects/lens_app.ts b/src/platform/packages/shared/kbn-scout/src/playwright/page_objects/lens_app.ts new file mode 100644 index 0000000000000..b874e78c20eaa --- /dev/null +++ b/src/platform/packages/shared/kbn-scout/src/playwright/page_objects/lens_app.ts @@ -0,0 +1,26 @@ +/* + * 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 type { ScoutPage } from '..'; + +export class LensApp { + constructor(private readonly page: ScoutPage) {} + + getConvertToEsqlButton() { + return this.page.getByRole('button', { name: 'Convert to ES|QL' }); + } + + getConvertToEsqModal() { + return this.page.getByTestId('lnsConvertToEsqlModal'); + } + + getConvertToEsqModalConfirmButton() { + return this.page.getByTestId('confirmModalConfirmButton'); + } +} diff --git a/x-pack/platform/plugins/shared/lens/moon.yml b/x-pack/platform/plugins/shared/lens/moon.yml index 8c26b5cede440..dd8dc9485a66d 100644 --- a/x-pack/platform/plugins/shared/lens/moon.yml +++ b/x-pack/platform/plugins/shared/lens/moon.yml @@ -150,6 +150,7 @@ dependsOn: - '@kbn/controls-constants' - '@kbn/core-base-browser-mocks' - '@kbn/react-kibana-context-env' + - '@kbn/scout' tags: - plugin - prod @@ -162,6 +163,7 @@ fileGroups: - common/**/* - public/**/* - server/**/* + - test/scout/**/* - '!target/**/*' tasks: jest: diff --git a/x-pack/platform/plugins/shared/lens/public/app_plugin/shared/edit_on_the_fly/convert_to_esql_modal.tsx b/x-pack/platform/plugins/shared/lens/public/app_plugin/shared/edit_on_the_fly/convert_to_esql_modal.tsx index 4cb6b6788dc3c..3414788c7ada8 100644 --- a/x-pack/platform/plugins/shared/lens/public/app_plugin/shared/edit_on_the_fly/convert_to_esql_modal.tsx +++ b/x-pack/platform/plugins/shared/lens/public/app_plugin/shared/edit_on_the_fly/convert_to_esql_modal.tsx @@ -224,6 +224,7 @@ export const ConvertToEsqlModal: React.FunctionComponent<{ defaultMessage: 'Switch to query mode', })} confirmButtonDisabled={!isConfirmButtonEnabled} + data-test-subj="lnsConvertToEsqlModal" >

{i18n.translate('xpack.lens.config.queryModeDescription', { diff --git a/x-pack/platform/plugins/shared/lens/test/scout/README.md b/x-pack/platform/plugins/shared/lens/test/scout/README.md new file mode 100644 index 0000000000000..6fd596bf3ccfd --- /dev/null +++ b/x-pack/platform/plugins/shared/lens/test/scout/README.md @@ -0,0 +1,15 @@ +# Lens Scout tests + +This directory contains Scout tests for the Lens plugin. + +## Running the tests + +### Run server +``` +node scripts/scout.js start-server --stateful +``` + +### Run tests +``` +npx playwright test --project local --grep @ess --config x-pack/platform/plugins/shared/lens/test/scout/ui/ --ui +``` \ No newline at end of file diff --git a/x-pack/platform/plugins/shared/lens/test/scout/ui/fixtures/esql_conversion_dashboard.json b/x-pack/platform/plugins/shared/lens/test/scout/ui/fixtures/esql_conversion_dashboard.json new file mode 100644 index 0000000000000..98cf993277827 --- /dev/null +++ b/x-pack/platform/plugins/shared/lens/test/scout/ui/fixtures/esql_conversion_dashboard.json @@ -0,0 +1,69 @@ +{ + "attributes": { + "allowHidden": false, + "fieldAttrs": "{}", + "fieldFormatMap": "{}", + "fields": "[]", + "name": "logstash-*", + "runtimeFieldMap": "{}", + "sourceFilters": "[]", + "timeFieldName": "@timestamp", + "title": "logstash-*" + }, + "coreMigrationVersion": "8.8.0", + "created_at": "2026-01-23T15:58:55.175Z", + "created_by": "u_EWATCHX9oIEsmcXj8aA1FkcaY3DE-XEpsiGTjrR2PmM_0", + "id": "7d4752fe-05bd-4472-9bcb-fb347412f558", + "managed": false, + "references": [], + "type": "index-pattern", + "typeMigrationVersion": "8.0.0", + "updated_at": "2026-01-23T15:58:55.175Z", + "updated_by": "u_EWATCHX9oIEsmcXj8aA1FkcaY3DE-XEpsiGTjrR2PmM_0", + "version": "WzI4LDFd" +} + +{ + "accessControl": { + "accessMode": "default", + "owner": "u_EWATCHX9oIEsmcXj8aA1FkcaY3DE-XEpsiGTjrR2PmM_0" + }, + "attributes": { + "controlGroupInput": { + "panelsJSON": "{}" + }, + "description": "", + "kibanaSavedObjectMeta": { + "searchSourceJSON": "{\"query\":{\"query\":\"\",\"language\":\"kuery\"}}" + }, + "optionsJSON": "{\"hidePanelTitles\":false,\"useMargins\":true,\"autoApplyFilters\":true,\"syncColors\":false,\"syncCursor\":true,\"syncTooltips\":false}", + "panelsJSON": "[{\"type\":\"lens\",\"embeddableConfig\":{\"enhancements\":{\"dynamicActions\":{\"events\":[]}},\"syncColors\":false,\"syncCursor\":true,\"syncTooltips\":false,\"filters\":[],\"query\":{\"query\":\"\",\"language\":\"kuery\"},\"attributes\":{\"title\":\"\",\"visualizationType\":\"lnsMetric\",\"type\":\"lens\",\"references\":[{\"type\":\"index-pattern\",\"id\":\"7d4752fe-05bd-4472-9bcb-fb347412f558\",\"name\":\"indexpattern-datasource-layer-956270fb-67f2-4b48-a24d-333a205a1993\"}],\"state\":{\"visualization\":{\"layerId\":\"956270fb-67f2-4b48-a24d-333a205a1993\",\"layerType\":\"data\",\"metricAccessor\":\"940aaabc-f1f5-4a45-8d44-0dfeee27ee7a\",\"maxAccessor\":\"14999fb7-ede6-49d8-9ebd-eba41ae6e780\",\"showBar\":true,\"secondaryTrend\":{\"type\":\"none\"},\"secondaryLabelPosition\":\"before\"},\"query\":{\"query\":\"\",\"language\":\"kuery\"},\"filters\":[],\"datasourceStates\":{\"formBased\":{\"layers\":{\"956270fb-67f2-4b48-a24d-333a205a1993\":{\"columns\":{\"940aaabc-f1f5-4a45-8d44-0dfeee27ee7a\":{\"label\":\"Average of bytes\",\"dataType\":\"number\",\"operationType\":\"average\",\"sourceField\":\"bytes\",\"isBucketed\":false,\"params\":{\"emptyAsNull\":true}},\"14999fb7-ede6-49d8-9ebd-eba41ae6e780\":{\"label\":\"Static value: 10000\",\"dataType\":\"number\",\"operationType\":\"static_value\",\"isBucketed\":false,\"params\":{\"value\":\"10000\",\"emptyAsNull\":true},\"references\":[]}},\"columnOrder\":[\"940aaabc-f1f5-4a45-8d44-0dfeee27ee7a\",\"14999fb7-ede6-49d8-9ebd-eba41ae6e780\"],\"incompleteColumns\":{},\"sampling\":1}}},\"indexpattern\":{\"layers\":{}},\"textBased\":{\"layers\":{}}},\"internalReferences\":[],\"adHocDataViews\":{}},\"version\":1}},\"panelIndex\":\"fb4626b8-d8ce-42d3-913a-081af94cfb51\",\"gridData\":{\"x\":0,\"y\":0,\"w\":24,\"h\":15,\"i\":\"fb4626b8-d8ce-42d3-913a-081af94cfb51\"}}]", + "timeRestore": false, + "title": "ES|QL Conversion Dashboard" + }, + "coreMigrationVersion": "8.8.0", + "created_at": "2026-01-23T16:04:55.656Z", + "created_by": "u_EWATCHX9oIEsmcXj8aA1FkcaY3DE-XEpsiGTjrR2PmM_0", + "id": "3cda479c-8797-4492-99f4-2259e1377f85", + "managed": false, + "references": [ + { + "id": "7d4752fe-05bd-4472-9bcb-fb347412f558", + "name": "fb4626b8-d8ce-42d3-913a-081af94cfb51:indexpattern-datasource-layer-956270fb-67f2-4b48-a24d-333a205a1993", + "type": "index-pattern" + } + ], + "type": "dashboard", + "typeMigrationVersion": "10.3.0", + "updated_at": "2026-01-23T16:04:55.656Z", + "updated_by": "u_EWATCHX9oIEsmcXj8aA1FkcaY3DE-XEpsiGTjrR2PmM_0", + "version": "WzM0LDFd" +} + +{ + "excludedObjects": [], + "excludedObjectsCount": 0, + "exportedCount": 2, + "missingRefCount": 0, + "missingReferences": [] +} diff --git a/x-pack/platform/plugins/shared/lens/test/scout/ui/playwright.config.ts b/x-pack/platform/plugins/shared/lens/test/scout/ui/playwright.config.ts new file mode 100644 index 0000000000000..75a7694d12043 --- /dev/null +++ b/x-pack/platform/plugins/shared/lens/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/lens/test/scout/ui/tests/esql_conversion.spec.ts b/x-pack/platform/plugins/shared/lens/test/scout/ui/tests/esql_conversion.spec.ts new file mode 100644 index 0000000000000..073636cf1b108 --- /dev/null +++ b/x-pack/platform/plugins/shared/lens/test/scout/ui/tests/esql_conversion.spec.ts @@ -0,0 +1,81 @@ +/* + * 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 '@kbn/scout'; + +const ES_ARCHIVES = { + LOGSTASH: 'x-pack/platform/test/fixtures/es_archives/logstash_functional', +}; + +const DATA_VIEW_ID = { + LOGSTASH: 'logstash-*', +}; + +const LOGSTASH_IN_RANGE_DATES = { + from: 'Sep 19, 2015 @ 06:31:44.000', + to: 'Sep 23, 2015 @ 18:31:44.000', +}; + +const KBN_ARCHIVES = { + ESQL_CONVERSION_DASHBOARD: + 'x-pack/platform/plugins/shared/lens/test/scout/ui/fixtures/esql_conversion_dashboard.json', +}; +const ESQL_CONVERSION_DASHBOARD_TEST_ID = 'dashboardListingTitleLink-ES|QL-Conversion-Dashboard'; +const METRIC_VISUALIZATION_ID = 'fb4626b8-d8ce-42d3-913a-081af94cfb51'; + +test.describe('Lens ES|QL', { tag: ['@ess'] }, () => { + test.beforeAll(async ({ esArchiver, kbnClient, uiSettings, apiServices }) => { + await apiServices.core.settings({ + 'feature_flags.overrides': { + 'lens.enable_esql_conversion': 'true', + }, + }); + await esArchiver.loadIfNeeded(ES_ARCHIVES.LOGSTASH); + await kbnClient.importExport.load(KBN_ARCHIVES.ESQL_CONVERSION_DASHBOARD); + await uiSettings.set({ + defaultIndex: DATA_VIEW_ID.LOGSTASH, + 'dateFormat:tz': 'UTC', + 'timepicker:timeDefaults': `{ "from": "${LOGSTASH_IN_RANGE_DATES.from}", "to": "${LOGSTASH_IN_RANGE_DATES.to}"}`, + }); + }); + + test.afterAll(async ({ kbnClient, uiSettings }) => { + await uiSettings.unset('defaultIndex', 'dateFormat:tz', 'timepicker:timeDefaults'); + await kbnClient.savedObjects.cleanStandardList(); + }); + + test('should display ES|QL conversion modal', async ({ browserAuth, page, pageObjects }) => { + await browserAuth.loginAsPrivilegedUser(); + + const { dashboard, lens } = pageObjects; + + // Navigate to the test dashboard + await dashboard.goto(); + await page.getByTestId(ESQL_CONVERSION_DASHBOARD_TEST_ID).click(); + + // Verify dashboard loaded with the test visualization + await dashboard.waitForPanelsToLoad(1); + + // Enter edit mode to access visualization actions + await dashboard.switchToEditMode(); + + await dashboard.openInlineEditor(METRIC_VISUALIZATION_ID); + + await lens.getConvertToEsqlButton().click(); + + await expect(lens.getConvertToEsqModal()).toBeVisible(); + + await lens.getConvertToEsqModalConfirmButton().click(); + + await expect(lens.getConvertToEsqModal()).toBeHidden(); + + // TODO: Add conversion assertions once logic is implemented (https://github.com/elastic/kibana/pull/248078) + // For now, this test only verifies the UI flow up to modal interaction + }); +}); diff --git a/x-pack/platform/plugins/shared/lens/tsconfig.json b/x-pack/platform/plugins/shared/lens/tsconfig.json index cf297dad1c828..5fe3b64e7303d 100644 --- a/x-pack/platform/plugins/shared/lens/tsconfig.json +++ b/x-pack/platform/plugins/shared/lens/tsconfig.json @@ -3,7 +3,14 @@ "compilerOptions": { "outDir": "target/types" }, - "include": ["*.ts", "common/**/*", "public/**/*", "server/**/*", "../../../../../typings/**/*"], + "include": [ + "*.ts", + "common/**/*", + "public/**/*", + "server/**/*", + "../../../../../typings/**/*", + "test/scout/**/*", + ], "kbn_references": [ "@kbn/spaces-plugin", "@kbn/core", @@ -137,6 +144,7 @@ "@kbn/controls-constants", "@kbn/core-base-browser-mocks", "@kbn/react-kibana-context-env", + "@kbn/scout", ], "exclude": ["target/**/*"] }