Skip to content

Commit

Permalink
Add OTel K8S e2e test for Ensemble (#206756)
Browse files Browse the repository at this point in the history
This adds an e2e test for [the Ensemble
workflow](https://github.com/elastic/ensemble/actions/workflows/nightly.yml)
to cover stack installation part of the OTel K8S quickstart flow.

Besides that I've replaced the retry logic for K8S EA and Auto Detect
flow with a simple timeouts to workaround the missing data issue on the
CTA pages (host details and k8s dashboard) after finishing the
onboarding flow. I've also simplified assertions on the CTA pages.
  • Loading branch information
mykolaharmash authored Jan 17, 2025
1 parent cd71ca9 commit ad30ed8
Show file tree
Hide file tree
Showing 11 changed files with 230 additions and 131 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,19 @@ test('Auto-detect logs and metrics', async ({ page, onboardingHomePage, autoDete
fs.writeFileSync(outputPath, clipboardData);

await autoDetectFlowPage.assertReceivedDataIndicator();

/**
* Host Details page sometime shows "No Data"
* even when we've detected data during
* the onboarding flow. This is most prominent
* in a test script which click on the "Explore Data"
* CTA immediately. Having a timeout before going
* to the Host Details page "solves" the issue.
* 2 minutes is generous and should be more then enough
* for the data to propagate everywhere.
*/
await page.waitForTimeout(2 * 60000);

await autoDetectFlowPage.clickAutoDetectSystemIntegrationCTA();

/**
Expand All @@ -44,22 +57,5 @@ test('Auto-detect logs and metrics', async ({ page, onboardingHomePage, autoDete
*/
const hostDetailsPage = new HostDetailsPage(await page.waitForEvent('popup'));

/**
* There is a glitch on the Hosts page where it can show "No data"
* screen even though data is available and it can show it with a delay
* after the Hosts page layout was loaded. This workaround waits for
* the No Data screen to be visible, and if so - reloads the page.
* If the No Data screen does not appear, the test can proceed normally.
* Seems like some caching issue with the Hosts page.
*/
try {
await hostDetailsPage.noData().waitFor({ state: 'visible', timeout: 10000 });
await hostDetailsPage.page.waitForTimeout(2000);
await hostDetailsPage.page.reload();
} catch {
/* Ignore if "No Data" screen never showed up */
}

await hostDetailsPage.clickHostDetailsLogsTab();
await hostDetailsPage.assertHostDetailsLogsStream();
await hostDetailsPage.assertCpuPercentageNotEmpty();
});
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,18 @@ import { SpaceSelector } from '../pom/components/space_selector.component';
import { KubernetesOverviewDashboardPage } from '../pom/pages/kubernetes_overview_dashboard.page';
import { AutoDetectFlowPage } from '../pom/pages/auto_detect_flow.page';
import { KubernetesEAFlowPage } from '../pom/pages/kubernetes_ea_flow.page';
import { OtelKubernetesFlowPage } from '../pom/pages/otel_kubernetes_flow.page';
import { OtelKubernetesOverviewDashboardPage } from '../pom/pages/otel_kubernetes_overview_dashboard.page';

export const test = base.extend<{
headerBar: HeaderBar;
spaceSelector: SpaceSelector;
onboardingHomePage: OnboardingHomePage;
autoDetectFlowPage: AutoDetectFlowPage;
kubernetesEAFlowPage: KubernetesEAFlowPage;
otelKubernetesFlowPage: OtelKubernetesFlowPage;
kubernetesOverviewDashboardPage: KubernetesOverviewDashboardPage;
otelKubernetesOverviewDashboardPage: OtelKubernetesOverviewDashboardPage;
}>({
headerBar: async ({ page }, use) => {
await use(new HeaderBar(page));
Expand All @@ -41,7 +45,15 @@ export const test = base.extend<{
await use(new KubernetesEAFlowPage(page));
},

otelKubernetesFlowPage: async ({ page }, use) => {
await use(new OtelKubernetesFlowPage(page));
},

kubernetesOverviewDashboardPage: async ({ page }, use) => {
await use(new KubernetesOverviewDashboardPage(page));
},

otelKubernetesOverviewDashboardPage: async ({ page }, use) => {
await use(new OtelKubernetesOverviewDashboardPage(page));
},
});
Original file line number Diff line number Diff line change
Expand Up @@ -45,22 +45,19 @@ test('Kubernetes EA', async ({
fs.writeFileSync(outputPath, clipboardData);

await kubernetesEAFlowPage.assertReceivedDataIndicatorKubernetes();
await kubernetesEAFlowPage.clickKubernetesAgentCTA();

await kubernetesOverviewDashboardPage.openNodesInspector();
/**
* There might be a case that dashboard still does not show
* the data even though it was ingested already. This usually
* happens during in the test when navigation from the onboarding
* happens during the test when navigation from the onboarding
* flow to the dashboard happens almost immediately.
* Waiting for a few seconds and reloading the page handles
* this case and makes the test a bit more robust.
* Having a timeout before going to the dashboard "solves"
* the issue. 2 minutes is generous and should be more then enough
* for the data to propagate everywhere.
*/
try {
await kubernetesOverviewDashboardPage.assertNodesNoResultsNotVisible();
} catch {
await kubernetesOverviewDashboardPage.page.waitForTimeout(2000);
await kubernetesOverviewDashboardPage.page.reload();
}
await kubernetesOverviewDashboardPage.assetNodesInspectorStatusTableCells();
await page.waitForTimeout(2 * 60000);

await kubernetesEAFlowPage.clickKubernetesAgentCTA();

await kubernetesOverviewDashboardPage.assertNodesPanelNotEmpty();
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
/*
* 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 fs from 'node:fs';
import path from 'node:path';
import { test } from './fixtures/base_page';
import { assertEnv } from '../lib/assert_env';

test.beforeEach(async ({ page }) => {
await page.goto(`${process.env.KIBANA_BASE_URL}/app/observabilityOnboarding`);
});

test('Otel Kubernetes', async ({
page,
onboardingHomePage,
otelKubernetesFlowPage,
otelKubernetesOverviewDashboardPage,
}) => {
assertEnv(process.env.ARTIFACTS_FOLDER, 'ARTIFACTS_FOLDER is not defined.');

const fileName = 'code_snippet_otel_kubernetes.sh';
const outputPath = path.join(__dirname, '..', process.env.ARTIFACTS_FOLDER, fileName);

await onboardingHomePage.selectKubernetesUseCase();
await onboardingHomePage.selectOtelKubernetesQuickstart();

await otelKubernetesFlowPage.copyHelmRepositorySnippetToClipboard();
const helmRepoSnippet = (await page.evaluate('navigator.clipboard.readText()')) as string;

await otelKubernetesFlowPage.copyInstallStackSnippetToClipboard();
const installStackSnippet = (await page.evaluate('navigator.clipboard.readText()')) as string;

const codeSnippet = `${helmRepoSnippet}\n${installStackSnippet}`;

/**
* Ensemble story watches for the code snippet file
* to be created and then executes it
*/
fs.writeFileSync(outputPath, codeSnippet);

/**
* There is no explicit data ingest indication
* in the flow, so we need to rely on a timeout.
* 3 minutes should be enough for the stack to be
* created and to start pushing data.
*/
await page.waitForTimeout(3 * 60000);

await otelKubernetesFlowPage.clickClusterOverviewDashboardCTA();
await otelKubernetesOverviewDashboardPage.assertNodesPanelNotEmpty();
});
Original file line number Diff line number Diff line change
Expand Up @@ -5,47 +5,46 @@
* 2.0.
*/

import { expect, Page } from '@playwright/test';
import { expect, type Page, type Locator } from '@playwright/test';

export class AutoDetectFlowPage {
page: Page;

private readonly copyToClipboardButton: Locator;
private readonly receivedDataIndicator: Locator;
private readonly autoDetectSystemIntegrationActionLink: Locator;
private readonly codeBlock: Locator;

constructor(page: Page) {
this.page = page;
}

private readonly copyToClipboardButton = () =>
this.page.getByTestId('observabilityOnboardingCopyToClipboardButton');

private readonly receivedDataIndicator = () =>
this.page
this.copyToClipboardButton = this.page.getByTestId(
'observabilityOnboardingCopyToClipboardButton'
);
this.receivedDataIndicator = this.page
.getByTestId('observabilityOnboardingAutoDetectPanelDataReceivedProgressIndicator')
.getByText('Your data is ready to explore!');

private readonly autoDetectSystemIntegrationActionLink = () =>
this.page.getByTestId(
this.autoDetectSystemIntegrationActionLink = this.page.getByTestId(
'observabilityOnboardingDataIngestStatusActionLink-inventory-host-details'
);

private readonly codeBlock = () =>
this.page.getByTestId('observabilityOnboardingAutoDetectPanelCodeSnippet');
this.codeBlock = this.page.getByTestId('observabilityOnboardingAutoDetectPanelCodeSnippet');
}

public async copyToClipboard() {
await this.copyToClipboardButton().click();
await this.copyToClipboardButton.click();
}

public async assertVisibilityCodeBlock() {
await expect(this.codeBlock(), 'Code block should be visible').toBeVisible();
await expect(this.codeBlock, 'Code block should be visible').toBeVisible();
}

public async assertReceivedDataIndicator() {
await expect(
this.receivedDataIndicator(),
this.receivedDataIndicator,
'Received data indicator should be visible'
).toBeVisible();
}

public async clickAutoDetectSystemIntegrationCTA() {
await this.autoDetectSystemIntegrationActionLink().click();
await this.autoDetectSystemIntegrationActionLink.click();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,34 +5,23 @@
* 2.0.
*/

import { expect, Page } from '@playwright/test';
import { expect, type Page, type Locator } from '@playwright/test';

export class HostDetailsPage {
page: Page;

public readonly hostDetailsLogsTab = () => this.page.getByTestId('infraAssetDetailsLogsTab');

private readonly hostDetailsLogsStream = () => this.page.getByTestId('logStream');

public readonly noData = () => this.page.getByTestId('kbnNoDataPage');
private readonly cpuPercentageValue: Locator;

constructor(page: Page) {
this.page = page;
}

public async clickHostDetailsLogsTab() {
await this.hostDetailsLogsTab().click();
this.cpuPercentageValue = this.page
.getByTestId('infraAssetDetailsKPIcpuUsage')
.locator('.echMetricText__value');
}

public async assertHostDetailsLogsStream() {
await expect(
this.hostDetailsLogsStream(),
'Host details log stream should be visible'
/**
* Using toBeAttached() instead of toBeVisible() because the element
* we're selecting here has a bit weird layout with 0 height and
* overflowing child elements. 0 height makes toBeVisible() fail.
*/
).toBeAttached();
public async assertCpuPercentageNotEmpty() {
await expect(this.cpuPercentageValue).toBeVisible();
expect(await this.cpuPercentageValue.textContent()).toMatch(/\d+%$/);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,47 +5,47 @@
* 2.0.
*/

import { expect, Page } from '@playwright/test';
import { expect, type Page, type Locator } from '@playwright/test';

export class KubernetesEAFlowPage {
page: Page;

private readonly receivedDataIndicatorKubernetes: Locator;
private readonly kubernetesAgentExploreDataActionLink: Locator;
private readonly codeBlock: Locator;
private readonly copyToClipboardButton: Locator;

constructor(page: Page) {
this.page = page;
}

private readonly receivedDataIndicatorKubernetes = () =>
this.page
this.receivedDataIndicatorKubernetes = this.page
.getByTestId('observabilityOnboardingKubernetesPanelDataProgressIndicator')
.getByText('We are monitoring your cluster');

private readonly kubernetesAgentExploreDataActionLink = () =>
this.page.getByTestId(
this.kubernetesAgentExploreDataActionLink = this.page.getByTestId(
'observabilityOnboardingDataIngestStatusActionLink-kubernetes-f4dc26db-1b53-4ea2-a78b-1bfab8ea267c'
);

private readonly codeBlock = () =>
this.page.getByTestId('observabilityOnboardingKubernetesPanelCodeSnippet');

private readonly copyToClipboardButton = () =>
this.page.getByTestId('observabilityOnboardingCopyToClipboardButton');
this.codeBlock = this.page.getByTestId('observabilityOnboardingKubernetesPanelCodeSnippet');
this.copyToClipboardButton = this.page.getByTestId(
'observabilityOnboardingCopyToClipboardButton'
);
}

public async assertVisibilityCodeBlock() {
await expect(this.codeBlock(), 'Code block should be visible').toBeVisible();
await expect(this.codeBlock, 'Code block should be visible').toBeVisible();
}

public async copyToClipboard() {
await this.copyToClipboardButton().click();
await this.copyToClipboardButton.click();
}

public async assertReceivedDataIndicatorKubernetes() {
await expect(
this.receivedDataIndicatorKubernetes(),
this.receivedDataIndicatorKubernetes,
'Received data indicator should be visible'
).toBeVisible();
}

public async clickKubernetesAgentCTA() {
await this.kubernetesAgentExploreDataActionLink().click();
await this.kubernetesAgentExploreDataActionLink.click();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,44 +5,22 @@
* 2.0.
*/

import { expect, Page } from '@playwright/test';
import { expect, type Page, type Locator } from '@playwright/test';

export class KubernetesOverviewDashboardPage {
page: Page;

private readonly nodesPanelChart: Locator;

constructor(page: Page) {
this.page = page;
}

private readonly nodesPanelHeader = () => this.page.getByTestId('embeddablePanelHeading-Nodes');

private readonly nodesInspectorButton = () =>
this.page
.getByTestId('embeddablePanelHoverActions-Nodes')
.getByTestId('embeddablePanelAction-openInspector');

private readonly nodesInspectorTableNoResults = () =>
this.page.getByTestId('inspectorTable').getByText('No items found');

private readonly nodesInspectorTableStatusTableCells = () =>
this.page.getByTestId('inspectorTable').getByText('Status');

public async assertNodesNoResultsNotVisible() {
await expect(
this.nodesInspectorTableNoResults(),
'Nodes "No results" message should not be visible'
).toBeHidden();
}

public async openNodesInspector() {
await this.nodesPanelHeader().hover();
await this.nodesInspectorButton().click();
this.nodesPanelChart = this.page
.locator(`#panel-7116207b-48ce-4d93-9fbd-26d73af1c185`)
.getByTestId('xyVisChart');
}

public async assetNodesInspectorStatusTableCells() {
await expect(
this.nodesInspectorTableStatusTableCells(),
'Status table cell should exist'
).toBeVisible();
async assertNodesPanelNotEmpty() {
await expect(this.nodesPanelChart).toBeVisible();
}
}
Loading

0 comments on commit ad30ed8

Please sign in to comment.