diff --git a/tests/suites/paginatedTable/paginatedTable.ts b/tests/suites/paginatedTable/paginatedTable.ts index 2a9f95533..b4ad38baf 100644 --- a/tests/suites/paginatedTable/paginatedTable.ts +++ b/tests/suites/paginatedTable/paginatedTable.ts @@ -245,3 +245,9 @@ export class ClusterStorageTable extends PaginatedTable { super(page, '.ydb-cluster'); } } + +export class DiagnosticsNodesTable extends PaginatedTable { + constructor(page: Page) { + super(page, '.kv-tenant-diagnostics__page-wrapper'); + } +} diff --git a/tests/suites/tenant/diagnostics/Diagnostics.ts b/tests/suites/tenant/diagnostics/Diagnostics.ts index 5737cac17..9ada27c5b 100644 --- a/tests/suites/tenant/diagnostics/Diagnostics.ts +++ b/tests/suites/tenant/diagnostics/Diagnostics.ts @@ -1,6 +1,9 @@ import type {Locator, Page} from '@playwright/test'; import {retryAction} from '../../../utils/retryAction'; +import {MemoryViewer} from '../../memoryViewer/MemoryViewer'; +import {NodesPage} from '../../nodes/NodesPage'; +import {StoragePage} from '../../storage/StoragePage'; import {VISIBILITY_TIMEOUT} from '../TenantPage'; export enum DiagnosticsTab { @@ -13,6 +16,7 @@ export enum DiagnosticsTab { Tablets = 'Tablets', HotKeys = 'Hot keys', Describe = 'Describe', + Storage = 'Storage', } export class Table { @@ -114,6 +118,9 @@ export enum QueriesSwitch { export class Diagnostics { table: Table; + storage: StoragePage; + nodes: NodesPage; + memoryViewer: MemoryViewer; private tabs: Locator; private schemaViewer: Locator; @@ -122,8 +129,15 @@ export class Diagnostics { private primaryKeys: Locator; private refreshButton: Locator; private autoRefreshSelect: Locator; + private cpuCard: Locator; + private storageCard: Locator; + private memoryCard: Locator; + private healthcheckCard: Locator; constructor(page: Page) { + this.storage = new StoragePage(page); + this.nodes = new NodesPage(page); + this.memoryViewer = new MemoryViewer(page); this.tabs = page.locator('.kv-tenant-diagnostics__tabs'); this.tableControls = page.locator('.ydb-table-with-controls-layout__controls'); this.schemaViewer = page.locator('.schema-viewer'); @@ -132,6 +146,12 @@ export class Diagnostics { this.refreshButton = page.locator('button[aria-label="Refresh"]'); this.autoRefreshSelect = page.locator('.g-select'); this.table = new Table(page.locator('.object-general')); + + // Info tab cards + this.cpuCard = page.locator('.metrics-cards__tab:has-text("CPU")'); + this.storageCard = page.locator('.metrics-cards__tab:has-text("Storage")'); + this.memoryCard = page.locator('.metrics-cards__tab:has-text("Memory")'); + this.healthcheckCard = page.locator('.metrics-cards__tab:has-text("Healthcheck")'); } async isSchemaViewerVisible() { @@ -184,4 +204,47 @@ export class Diagnostics { const optionLocator = this.autoRefreshSelect.locator(`text=${option}`); await optionLocator.click(); } + + async areInfoCardsVisible() { + await this.cpuCard.waitFor({state: 'visible', timeout: VISIBILITY_TIMEOUT}); + await this.storageCard.waitFor({state: 'visible', timeout: VISIBILITY_TIMEOUT}); + await this.memoryCard.waitFor({state: 'visible', timeout: VISIBILITY_TIMEOUT}); + await this.healthcheckCard.waitFor({state: 'visible', timeout: VISIBILITY_TIMEOUT}); + return true; + } + + async getResourceUtilization() { + const cpuSystem = await this.cpuCard + .locator('.ydb-metrics-card__metric:has-text("System") .progress-viewer__text') + .textContent(); + const cpuUser = await this.cpuCard + .locator('.ydb-metrics-card__metric:has-text("User") .progress-viewer__text') + .textContent(); + const cpuIC = await this.cpuCard + .locator('.ydb-metrics-card__metric:has-text("IC") .progress-viewer__text') + .textContent(); + const storage = await this.storageCard + .locator('.ydb-metrics-card__metric:has-text("SSD") .progress-viewer__text') + .textContent(); + const memory = await this.memoryCard + .locator('.ydb-metrics-card__metric:has-text("Process") .progress-viewer__text') + .textContent(); + + return { + cpu: { + system: cpuSystem?.trim() || '', + user: cpuUser?.trim() || '', + ic: cpuIC?.trim() || '', + }, + storage: storage?.trim() || '', + memory: memory?.trim() || '', + }; + } + + async getHealthcheckStatus() { + const statusElement = this.healthcheckCard.locator( + '.healthcheck__self-check-status-indicator', + ); + return (await statusElement.textContent())?.trim() || ''; + } } diff --git a/tests/suites/tenant/diagnostics/diagnostics.test.ts b/tests/suites/tenant/diagnostics/diagnostics.test.ts index f2b5be6cf..6b1720d04 100644 --- a/tests/suites/tenant/diagnostics/diagnostics.test.ts +++ b/tests/suites/tenant/diagnostics/diagnostics.test.ts @@ -1,6 +1,7 @@ import {expect, test} from '@playwright/test'; import {dsVslotsSchema, tenantName} from '../../../utils/constants'; +import {DiagnosticsNodesTable} from '../../paginatedTable/paginatedTable'; import {NavigationTabs, TenantPage} from '../TenantPage'; import {longRunningQuery} from '../constants'; import {QueryEditor} from '../queryEditor/models/QueryEditor'; @@ -8,6 +9,106 @@ import {QueryEditor} from '../queryEditor/models/QueryEditor'; import {Diagnostics, DiagnosticsTab, QueriesSwitch} from './Diagnostics'; test.describe('Diagnostics tab', async () => { + test('Info tab shows main page elements', async ({page}) => { + const pageQueryParams = { + schema: tenantName, + database: tenantName, + tenantPage: 'diagnostics', + }; + const tenantPage = new TenantPage(page); + await tenantPage.goto(pageQueryParams); + + const diagnostics = new Diagnostics(page); + await diagnostics.clickTab(DiagnosticsTab.Info); + await expect(diagnostics.areInfoCardsVisible()).resolves.toBe(true); + }); + + test('Info tab shows resource utilization', async ({page}) => { + const pageQueryParams = { + schema: tenantName, + database: tenantName, + tenantPage: 'diagnostics', + }; + const tenantPage = new TenantPage(page); + await tenantPage.goto(pageQueryParams); + + const diagnostics = new Diagnostics(page); + await diagnostics.clickTab(DiagnosticsTab.Info); + + const utilization = await diagnostics.getResourceUtilization(); + expect(utilization.cpu.system).toMatch(/\d+(\.\d+)? \/ \d+/); + expect(utilization.cpu.user).toMatch(/\d+(\.\d+)? \/ \d+/); + expect(utilization.cpu.ic).toMatch(/\d+(\.\d+)? \/ \d+/); + expect(utilization.storage).toBeTruthy(); + expect(utilization.memory).toMatch(/\d+ \/ \d+\s*GB/); + }); + + test('Info tab shows healthcheck status', async ({page}) => { + const pageQueryParams = { + schema: tenantName, + database: tenantName, + tenantPage: 'diagnostics', + }; + const tenantPage = new TenantPage(page); + await tenantPage.goto(pageQueryParams); + + const diagnostics = new Diagnostics(page); + await diagnostics.clickTab(DiagnosticsTab.Info); + + const status = await diagnostics.getHealthcheckStatus(); + expect(status).toBe('GOOD'); + }); + + test('Storage tab shows Groups and Nodes views', async ({page}) => { + const pageQueryParams = { + schema: tenantName, + database: tenantName, + tenantPage: 'diagnostics', + }; + const tenantPage = new TenantPage(page); + await tenantPage.goto(pageQueryParams); + + const diagnostics = new Diagnostics(page); + await diagnostics.clickTab(DiagnosticsTab.Storage); + + // Check Groups view + await diagnostics.storage.selectEntityType('Groups'); + await expect(diagnostics.storage.table).toBeVisible(); + + // Check Nodes view + await diagnostics.storage.selectEntityType('Nodes'); + await expect(diagnostics.storage.table).toBeVisible(); + }); + + test('Nodes tab shows nodes table with memory viewer', async ({page}) => { + const pageQueryParams = { + schema: tenantName, + database: tenantName, + tenantPage: 'diagnostics', + }; + const tenantPage = new TenantPage(page); + await tenantPage.goto(pageQueryParams); + + const diagnostics = new Diagnostics(page); + await diagnostics.clickTab(DiagnosticsTab.Nodes); + + // Check nodes table is visible + await expect(diagnostics.nodes.table).toBeVisible(); + + // Enable Memory column to show memory viewer + const paginatedTable = new DiagnosticsNodesTable(page); + await paginatedTable.waitForTableVisible(); + await paginatedTable.waitForTableData(); + const controls = paginatedTable.getControls(); + await controls.openColumnSetup(); + await controls.setColumnChecked('Memory'); + await controls.applyColumnVisibility(); + + // Check memory viewer is present and visible + await diagnostics.memoryViewer.waitForVisible(); + await expect(diagnostics.memoryViewer.isVisible()).resolves.toBe(true); + }); + test('Primary keys header is visible in Schema tab', async ({page}) => { const pageQueryParams = { schema: dsVslotsSchema,