Skip to content

Commit

Permalink
[scout] adding test helper @kbn/scout-oblt package and uptate onboa…
Browse files Browse the repository at this point in the history
…rding tests (elastic#209761)

## Summary

`@kbn/scout-oblt` is a test library that extends `@kbn/scout` with test
helpers specifically designed to test `Observability` applications in
Kibana. All Oblt plugins should only import from `@kbn/scout-oblt`

Its primary goal is to simplify the test development experience for
teams working on `Observability` plugins by providing custom Playwright
fixtures, page objects, and utilities tailored for Observability-related
testing scenarios.

Contributing:
- when Fixture/Page Object is sharable across all Solutions and Platform
(`fleetApi` fixture), it should be added in `@kbn/scout`
- when Fixture/Page Object is Oblt-specific but is shared across tests
under the multiple plugins (`OnboardingHome` page), it should be added
in `@kbn/scout-oblt`
- when Fixture/Page Object is only used in a single plugin (`onboarding`
internal APIs ?), it should be added in this plugin.

I also re-worked existing tests with few ideas in mind:
- Scout is **e2e testing tool** and should target primary e2e test
scenarios; We have _API integration tests_ to test multiple short
scenarios for APIs behavior (response, status code) and _jest/React
testing library_ to test components in isolation (elements rendering,
fields validation). Doing all the testing with e2e tool like Playwright
will dramatically affect cost efficiency and stability of tests, but
also slows overall CI execution and PRs delivery. The goal is to follow
testing pyramid and keep in mind its principles.
- We on purpose spin up new browser context for each `test` block to
make sure our **tests are independent**. Having too many short `test`
blocks in the file significantly slows down the execution: every block
triggers browser context, saml authentication, adding/removing Fleet
integrations (each call up to 2 seconds) and other beforeEach/afterEach
hooks. Real browser-based testing is expensive. It is not about putting
every step into 1 `test` block, but also not a Jest unit-test-style
design. When it is possible to group similar actions on the same page
and if it is a part of the same user flow - we should do it. It also
doesn't bring the testing value repeating the same UI steps multiple
times in different scenarios. _Our CI costs are critical to cut when it
is possible_
- Avoid **nesting describe** blocks: it complicates test readability and
also complicates for CI bot to properly skip the failing block (it will
skip the top level one). We encourage **Scout parallel test execution**
based on running test spec files in multiple workers, not the `test`
blocks within the same file. Having too many `test` blocks in the same
file will be slowly run in the single thread and in case of flakiness,
it means Team lose more test coverage than they probably expect.

Before (**59** test blocks - **8-8.5 min** per distro):
<img width="1709" alt="Screenshot 2025-02-08 at 18 01 40"
src="https://github.com/user-attachments/assets/5fd65a1c-85f9-4594-9dae-3f8e99a005ab"
/>

After (**15** test blocks - **3.5-4 min** per distro):
<img width="1578" alt="Screenshot 2025-02-10 at 18 14 42"
src="https://github.com/user-attachments/assets/6846898f-7dd2-4f6b-8bc5-d06741b0b120"
/>

For reviewers: updated tests are possible to run in 2 parallel workers
against the same Kibana/ES instance and run time is dropping to **2.5-3
min** 🚀 . It is up to UX-Logs team to decide if you want to keep
parallel run (new tests can be added either to parallel or sequential
run)
<img width="1578" alt="Screenshot 2025-02-11 at 12 14 30"
src="https://github.com/user-attachments/assets/e94113f2-d7f1-470e-a6d5-cb5154d99c41"
/>

---------

Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>
  • Loading branch information
dmlemeshko and kibanamachine authored Feb 11, 2025
1 parent 000e913 commit bd13e82
Show file tree
Hide file tree
Showing 41 changed files with 1,086 additions and 1,183 deletions.
4 changes: 3 additions & 1 deletion .buildkite/scripts/steps/functional/scout_ui_tests.sh
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,9 @@ done

# Observability Onboarding
for run_mode in "--stateful" "--serverless=oblt"; do
run_tests "Observability Onboarding" "x-pack/solutions/observability/plugins/observability_onboarding/ui_tests/playwright.config.ts" "$run_mode"
run_tests "Observability Onboarding: Parallel Workers" "x-pack/solutions/observability/plugins/observability_onboarding/ui_tests/parallel.playwright.config.ts" "$run_mode"
# Disabled while we don't have any tests under the config
# run_tests "Observability Onboarding" "x-pack/solutions/observability/plugins/observability_onboarding/ui_tests/playwright.config.ts" "$run_mode"
done


Expand Down
1 change: 1 addition & 0 deletions .github/CODEOWNERS
Original file line number Diff line number Diff line change
Expand Up @@ -919,6 +919,7 @@ x-pack/solutions/observability/packages/get_padded_alert_time_range_util @elasti
x-pack/solutions/observability/packages/kbn-alerts-grouping @elastic/response-ops
x-pack/solutions/observability/packages/kbn-custom-integrations @elastic/obs-ux-logs-team
x-pack/solutions/observability/packages/kbn-investigation-shared @elastic/obs-ux-management-team
x-pack/solutions/observability/packages/kbn-scout-oblt @elastic/appex-qa
x-pack/solutions/observability/packages/kbn-streams-schema @elastic/streams-program-team
x-pack/solutions/observability/packages/observability_ai/observability_ai_common @elastic/obs-ai-assistant
x-pack/solutions/observability/packages/observability_ai/observability_ai_server @elastic/obs-ai-assistant
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -1489,6 +1489,7 @@
"@kbn/reporting-mocks-server": "link:src/platform/packages/private/kbn-reporting/mocks_server",
"@kbn/scout": "link:packages/kbn-scout",
"@kbn/scout-info": "link:packages/kbn-scout-info",
"@kbn/scout-oblt": "link:x-pack/solutions/observability/packages/kbn-scout-oblt",
"@kbn/scout-reporting": "link:packages/kbn-scout-reporting",
"@kbn/security-api-integration-helpers": "link:x-pack/test/security_api_integration/packages/helpers",
"@kbn/serverless-storybook-config": "link:packages/serverless/storybook/config",
Expand Down
5 changes: 5 additions & 0 deletions packages/kbn-scout/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,3 +36,8 @@ export type {
ScoutServerConfig,
ScoutTestConfig,
} from './src/types';

// re-export from Playwright
export type { Locator } from 'playwright/test';

export { measurePerformance, measurePerformanceAsync } from './src/common';
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,9 @@
*/

import { mergeTests } from 'playwright/test';
import { coreWorkerFixtures, scoutSpaceParallelFixture } from './worker';
import { apiFixtures, coreWorkerFixtures, scoutSpaceParallelFixture } from './worker';
import type {
ApiParallelWorkerFixtures,
EsClient,
KbnClient,
KibanaUrl,
Expand All @@ -29,6 +30,7 @@ export const scoutParallelFixtures = mergeTests(
// worker scope fixtures
coreWorkerFixtures,
scoutSpaceParallelFixture,
apiFixtures,
// test scope fixtures
browserAuthFixture,
scoutPageParallelFixture,
Expand All @@ -42,7 +44,7 @@ export interface ScoutParallelTestFixtures {
pageObjects: PageObjects;
}

export interface ScoutParallelWorkerFixtures {
export interface ScoutParallelWorkerFixtures extends ApiParallelWorkerFixtures {
log: ScoutLogger;
config: ScoutTestConfig;
kbnUrl: KibanaUrl;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,13 @@
*/

import { mergeTests } from 'playwright/test';
import { coreWorkerFixtures, esArchiverFixture, uiSettingsFixture } from './worker';
import {
ApiFixtures,
apiFixtures,
coreWorkerFixtures,
esArchiverFixture,
uiSettingsFixture,
} from './worker';
import type {
EsArchiverFixture,
EsClient,
Expand All @@ -34,6 +40,8 @@ export const scoutFixtures = mergeTests(
coreWorkerFixtures,
esArchiverFixture,
uiSettingsFixture,
// api fixtures
apiFixtures,
// test scope fixtures
browserAuthFixture,
scoutPageFixture,
Expand All @@ -47,7 +55,7 @@ export interface ScoutTestFixtures {
pageObjects: PageObjects;
}

export interface ScoutWorkerFixtures {
export interface ScoutWorkerFixtures extends ApiFixtures {
log: ScoutLogger;
config: ScoutTestConfig;
kbnUrl: KibanaUrl;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
*/

import { Page } from '@playwright/test';
import { PathOptions } from '../../../../common/services/kibana_url';

/**
* Extends the Playwright 'Page' interface with methods specific to Kibana.
Expand All @@ -24,7 +25,7 @@ export type ScoutPage = Page & {
* @param options - Additional navigation options, passed directly to Playwright's `goto` method.
* @returns A Promise resolving to a Playwright `Response` or `null`.
*/
gotoApp: (appName: string, options?: Parameters<Page['goto']>[1]) => ReturnType<Page['goto']>;
gotoApp: (appName: string, pathOptions?: PathOptions) => ReturnType<Page['goto']>;
/**
* Waits for the Kibana loading spinner indicator to disappear.
* @returns A Promise resolving when the indicator is hidden.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
*/

import { Page, test as base } from '@playwright/test';
import { PathOptions } from '../../../../common/services/kibana_url';
import { ScoutPage } from '.';
import { KibanaUrl, ScoutLogger } from '../../worker';
import { ScoutSpaceParallelFixture } from '../../worker/scout_space';
Expand All @@ -29,8 +30,8 @@ export const scoutPageParallelFixture = base.extend<
const extendedPage = extendPlaywrightPage({ page, kbnUrl });

// Overriding navigation to specific Kibana apps: url should respect the Kibana Space id
extendedPage.gotoApp = (appName: string) =>
page.goto(kbnUrl.app(appName, { space: scoutSpace.id }));
extendedPage.gotoApp = (appName: string, pathOptions?: PathOptions) =>
page.goto(kbnUrl.app(appName, { space: scoutSpace.id, pathOptions }));

log.serviceLoaded(`scoutPage:${scoutSpace.id}`);
await use(extendedPage);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@

import { Page } from '@playwright/test';
import { subj } from '@kbn/test-subj-selector';
import { PathOptions } from '../../../../common/services/kibana_url';
import { KibanaUrl, ScoutLogger, coreWorkerFixtures } from '../../worker';
import { ScoutPage } from '.';

Expand Down Expand Up @@ -81,7 +82,8 @@ export function extendPlaywrightPage({
// Extend page with '@kbn/test-subj-selector' support
extendedPage.testSubj = extendPageWithTestSubject(page);
// Method to navigate to specific Kibana apps
extendedPage.gotoApp = (appName: string) => page.goto(kbnUrl.app(appName));
extendedPage.gotoApp = (appName: string, pathOptions?: PathOptions) =>
page.goto(kbnUrl.app(appName, { pathOptions }));
// Method to wait for global loading indicator to be hidden
extendedPage.waitForLoadingIndicatorHidden = () =>
extendedPage.testSubj.waitForSelector('globalLoadingIndicator-hidden', {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
/*
* 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 { measurePerformanceAsync } from '../../../../../common';
import { coreWorkerFixtures } from '../../core_fixtures';

export interface FleetApiFixture {
integration: {
install: (name: string) => Promise<void>;
delete: (name: string) => Promise<void>;
};
}

/**
* This fixture provides a helper to interact with the Fleet API.
*/
export const fleetApiFixture = coreWorkerFixtures.extend<{}, { fleetApi: FleetApiFixture }>({
fleetApi: [
async ({ kbnClient, log }, use) => {
const fleetApiHelper = {
integration: {
install: async (name: string) => {
await measurePerformanceAsync(
log,
`fleetApi.integration.install [${name}]`,
async () => {
await kbnClient.request({
method: 'POST',
path: `/api/fleet/epm/custom_integrations`,
body: {
force: true,
integrationName: name,
datasets: [
{ name: `${name}.access`, type: 'logs' },
{ name: `${name}.error`, type: 'metrics' },
{ name: `${name}.warning`, type: 'logs' },
],
},
});
}
);
},

delete: async (name: string) => {
await measurePerformanceAsync(
log,
`fleetApi.integration.delete [${name}]`,
async () => {
await kbnClient.request({
method: 'DELETE',
path: `/api/fleet/epm/packages/${name}`,
ignoreErrors: [400],
});
}
);
},
},
};

log.serviceLoaded('fleetApi');
await use(fleetApiHelper);
},
{ scope: 'worker' },
],
});
21 changes: 21 additions & 0 deletions packages/kbn-scout/src/playwright/fixtures/worker/apis/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
/*
* 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 { mergeTests } from 'playwright/test';
import { FleetApiFixture, fleetApiFixture } from './fleet';

export const apiFixtures = mergeTests(fleetApiFixture);

export interface ApiFixtures {
fleetApi: FleetApiFixture;
}

export interface ApiParallelWorkerFixtures {
fleetApi: FleetApiFixture;
}
3 changes: 3 additions & 0 deletions packages/kbn-scout/src/playwright/fixtures/worker/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,3 +25,6 @@ export type { UiSettingsFixture } from './ui_settings';

export { scoutSpaceParallelFixture } from './scout_space';
export type { ScoutSpaceParallelFixture } from './scout_space';

export { apiFixtures } from './apis';
export type { ApiFixtures, ApiParallelWorkerFixtures } from './apis';
2 changes: 2 additions & 0 deletions tsconfig.base.json
Original file line number Diff line number Diff line change
Expand Up @@ -1566,6 +1566,8 @@
"@kbn/scout/*": ["packages/kbn-scout/*"],
"@kbn/scout-info": ["packages/kbn-scout-info"],
"@kbn/scout-info/*": ["packages/kbn-scout-info/*"],
"@kbn/scout-oblt": ["x-pack/solutions/observability/packages/kbn-scout-oblt"],
"@kbn/scout-oblt/*": ["x-pack/solutions/observability/packages/kbn-scout-oblt/*"],
"@kbn/scout-reporting": ["packages/kbn-scout-reporting"],
"@kbn/scout-reporting/*": ["packages/kbn-scout-reporting/*"],
"@kbn/screenshot-mode-example-plugin": ["examples/screenshot_mode_example"],
Expand Down
45 changes: 45 additions & 0 deletions x-pack/solutions/observability/packages/kbn-scout-oblt/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
# @kbn/scout-oblt

`@kbn/scout-oblt` is a test library that extends `@kbn/scout` with test helpers specifically designed for `Observability` products in Kibana.

Its primary goal is to simplify the test development experience for teams working on `Observability` plugins by providing custom Playwright fixtures, page objects, and utilities tailored for Observability-related testing scenarios.

### Table of Contents

1. Folder Structure
2. How to Use
3. Contributing

### Folder Structure

The `@kbn/scout-oblt` structure includes the following key directories and files:

```
x-pack/solutions/observability/packages/kbn-scout-oblt/
├── src/
│ ├── playwright/
│ │ └── fixtures
│ │ │ └── test/
│ │ │ │ └── // Observability test-scope fixtures
│ │ │ └── worker/
│ │ │ │ └── // Observability worker-scope fixtures
│ │ │ └── single_thread_fixtures.ts
│ │ │ └── parallel_run_fixtures.ts
│ │ │ └── index.ts
│ │ └── page_objects/
│ │ │ └── // Observability pages
│ └── index.ts
├── package.json
├── tsconfig.json
```

### How to use

```
import { test } from '@kbn/scout-oblt';
test('verifies Observability Home loads', async ({ page, pageObjects }) => {
await pageObjects.onboardingHome.goto();
expect(await page.title()).toContain('Observability');
});
```
38 changes: 38 additions & 0 deletions x-pack/solutions/observability/packages/kbn-scout-oblt/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
/*
* 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 { test, spaceTest } from './src/playwright';
export type {
ObltPageObjects,
ObltTestFixtures,
ObltWorkerFixtures,
ObltParallelTestFixtures,
ObltParallelWorkerFixtures,
} from './src/playwright';

// re-export from @kbn/scout
export {
expect,
tags,
createPlaywrightConfig,
createLazyPageObject,
ingestTestDataHook,
} from '@kbn/scout';

export type {
EsClient,
KbnClient,
KibanaUrl,
ScoutLogger,
ScoutPage,
PageObjects,
ScoutServerConfig,
ScoutTestConfig,
ScoutPlaywrightOptions,
ScoutTestOptions,
Locator,
} from '@kbn/scout';
Original file line number Diff line number Diff line change
@@ -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.
*/

module.exports = {
preset: '@kbn/test/jest_node',
rootDir: '../../../../..',
roots: ['<rootDir>/x-pack/solutions/observability/packages/kbn-scout-oblt'],
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"type": "test-helper",
"id": "@kbn/scout-oblt",
"owner": "@elastic/appex-qa",
"devOnly": true
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"name": "@kbn/scout-oblt",
"private": true,
"version": "1.0.0",
"license": "Elastic License 2.0"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
/*
* 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 { test } from './single_thread_fixtures';
export { spaceTest } from './parallel_run_fixtures';
export * from './types';
Loading

0 comments on commit bd13e82

Please sign in to comment.