Files
openobserve/tests/ui-testing/pages/alertsPages/alertLibraryPage.js
Omkar Kesarkhaneandgithub-actions[bot] 305e0c2fc1 fix(alerts): faster alert library cards and a first-run path to the library (#16087)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-11-02 12:38:25 +00:00

357 lines
16 KiB
JavaScript

// Selectors and expects for the Alert Library page; specs never inline them.
import { expect } from '@playwright/test';
const testLogger = require('../../playwright-tests/utils/test-logger.js');
const { getAuthHeaders, getOrgIdentifier } = require('../../playwright-tests/utils/cloud-auth.js');
export class AlertLibraryPage {
constructor(page) {
this.page = page;
this.l = {
page: '[data-test="alert-library-page"]',
title: '[data-test="alert-library-title"]',
refresh: '[data-test="alert-library-refresh"]',
contribute: '[data-test="alert-library-contribute"]',
tab: '[data-test="alert-section-tab-alertLibrary"]',
// toolbar
search: '[data-test="alert-library-search"]',
searchField: '[data-test="alert-library-search"] input',
selectAllInView: '[data-test="alert-library-select-all-in-view"]',
selectionBar: '[data-test="alert-library-selection-bar"]',
clearSelection: '[data-test="alert-library-clear-selection"]',
addSelected: '[data-test="alert-library-add-selected"]',
// stat strip (readiness filter)
statReady: '[data-test="alert-library-stat-ready"]',
statNeedsData: '[data-test="alert-library-stat-missing"]',
statAll: '[data-test="alert-library-stat-all"]',
// grid + rail
grid: '[data-test="alert-library-grid"]',
railSeverity: '[data-test="alert-library-rail-severity"]',
railSearchCategories: '[data-test="alert-library-rail-search-categories"]',
railClearCategories: '[data-test="alert-library-rail-clear-categories"]',
// page states
error: '[data-test="alert-library-error"]',
emptyCatalog: '[data-test="alert-library-empty-catalog"]',
noResults: '[data-test="alert-library-no-results"]',
// drawer
drawer: '[data-test="alert-library-drawer"]',
drawerLoading: '[data-test="alert-library-drawer-loading"]',
drawerLoadFailed: '[data-test="alert-library-drawer-load-failed"]',
drawerPreview: '[data-test="alert-library-drawer-preview"]',
drawerEvaluation: '[data-test="alert-library-drawer-evaluation"]',
drawerNeedsData: '[data-test="alert-library-drawer-needs-data"]',
drawerAvailability: '[data-test="alert-library-drawer-availability"]',
drawerInstall: '[data-test="alert-library-drawer-install"]',
drawerCustomize: '[data-test="alert-library-drawer-customize"]',
// install wizard
dialog: '[data-test="alert-library-install-dialog"]',
folderStep: '[data-test="alert-library-install-folder-step"]',
tuneStep: '[data-test="alert-library-install-tune-step"]',
reviewStep: '[data-test="alert-library-install-review-step"]',
tuneToggle: '[data-test="alert-library-install-tune-toggle"]',
tuneFrequency: '[data-test="alert-library-install-tune-frequency"]',
tuneSilence: '[data-test="alert-library-install-tune-silence"]',
summary: '[data-test="alert-library-install-summary"]',
confirmLarge: '[data-test="alert-library-install-confirm-large"]',
next: '[data-test="alert-library-install-next"]',
run: '[data-test="alert-library-install-run"]',
retry: '[data-test="alert-library-install-retry"]',
done: '[data-test="alert-library-install-done"]',
cancel: '[data-test="alert-library-install-cancel"]',
// alert editor (the Customize hand-off target)
editorAlertName: '[data-test="add-alert-name-input-value"]',
};
}
// ── navigation ────────────────────────────────────────────────────────────
async openViaUrl() {
const base = process.env.ZO_BASE_URL || 'http://localhost:5080';
await this.page.goto(`${base}/web/alert-library?org_identifier=${getOrgIdentifier()}`);
await this.waitForGallery();
}
async openViaTab() {
// Wait for the tab to render before clicking — under cloud load the section
// tab strip can take longer than the click's own actionability window.
const tab = this.page.locator(this.l.tab);
await tab.waitFor({ state: 'visible', timeout: 30000 });
await tab.click();
await this.waitForGallery();
}
async waitForGallery() {
await expect(this.page.locator(this.l.page)).toBeVisible({ timeout: 30000 });
// Grid OR a terminal empty/error state — never hang on the skeleton.
await this.page
.locator(`${this.l.grid}, ${this.l.error}, ${this.l.emptyCatalog}, ${this.l.noResults}`)
.first()
.waitFor({ state: 'visible', timeout: 30000 });
}
// ── browse / filter / search ──────────────────────────────────────────────
async search(text) {
const field = this.page.locator(this.l.searchField);
await field.waitFor({ state: 'attached', timeout: 10000 });
await field.fill(text, { force: true });
await this.page.waitForTimeout(400); // 200ms debounce + render
}
async filterReady() { await this.page.locator(this.l.statReady).click(); }
async filterNeedsData() { await this.page.locator(this.l.statNeedsData).click(); }
async filterAll() { await this.page.locator(this.l.statAll).click(); }
async selectSeverity(id) {
await this.page.locator(`[data-test="alert-library-rail-severity-${id}"]`).click();
}
async toggleCategory(id) {
await this.page.locator(`[data-test="alert-library-rail-category-${id}"]`).click();
}
async searchCategories(text) {
const root = this.page.locator(this.l.railSearchCategories);
await root.waitFor({ state: 'attached', timeout: 10000 });
const inner = root.locator('input');
const target = (await inner.count()) ? inner.first() : root;
await target.fill(text, { force: true });
await this.page.waitForTimeout(300);
}
async clearCategories() {
await this.page.locator(this.l.railClearCategories).click();
}
async cardCount() {
return this.page.locator('[data-test^="alert-library-card-"][data-selected]').count();
}
// ── cards / selection ─────────────────────────────────────────────────────
card(id) { return this.page.locator(`[data-test="alert-library-card-${id}"]`); }
cardTitle(id) { return this.page.locator(`[data-test="alert-library-card-title-${id}"]`); }
cardAdd(id) { return this.page.locator(`[data-test="alert-library-card-add-${id}"]`); }
cardCustomize(id) { return this.page.locator(`[data-test="alert-library-card-customize-${id}"]`); }
needsDataChip(id) {
return this.card(id).locator('[data-test="alert-library-card-needs-data"]');
}
async firstCardId() {
const first = this.page.locator('[data-test^="alert-library-card-title-"]').first();
await first.waitFor({ state: 'visible', timeout: 15000 });
const dt = await first.getAttribute('data-test');
return dt.replace('alert-library-card-title-', '');
}
async addFromCard(id) {
await this.cardAdd(id).click();
await expect(this.page.locator(this.l.dialog)).toBeVisible({ timeout: 15000 });
}
async customizeFromCard(id) { await this.cardCustomize(id).click(); }
async selectCard(id) {
await this.page.locator(`[data-test="alert-library-select-${id}"]`).click();
}
async selectAllInViewToggle() {
await this.page.locator(this.l.selectAllInView).click();
}
async selectGroup(id) {
await this.page.locator(`[data-test="alert-library-select-group-${id}"]`).click();
}
async addSelected() {
await this.page.locator(this.l.addSelected).click();
await expect(this.page.locator(this.l.dialog)).toBeVisible({ timeout: 15000 });
}
async clearSelection() { await this.page.locator(this.l.clearSelection).click(); }
async selectedCountInBar() {
const bar = this.page.locator(this.l.selectionBar);
if (!(await bar.isVisible().catch(() => false))) return 0;
return Number((await bar.getAttribute('data-selected')) || 0);
}
async offscreenCountInBar() {
const bar = this.page.locator(this.l.selectionBar);
if (!(await bar.isVisible().catch(() => false))) return 0;
return Number((await bar.getAttribute('data-offscreen')) || 0);
}
async firstSelectGroup() {
await this.page.locator('[data-test^="alert-library-select-group-"]').first().click();
}
// ── drawer ────────────────────────────────────────────────────────────────
async openCard(id) {
await this.cardTitle(id).click();
await expect(this.page.locator(this.l.drawer)).toBeVisible({ timeout: 15000 });
await this.waitDrawerLoaded();
}
async waitDrawerLoaded() {
// Install button enables only once the file GET resolves.
await expect(this.page.locator(this.l.drawerInstall)).toBeEnabled({ timeout: 20000 });
}
async installFromDrawer() {
await this.page.locator(this.l.drawerInstall).click();
await expect(this.page.locator(this.l.dialog)).toBeVisible({ timeout: 15000 });
}
async customizeFromDrawer() {
await this.page.locator(this.l.drawerCustomize).click();
}
// ── install wizard ────────────────────────────────────────────────────────
async next() { await this.page.locator(this.l.next).click(); }
async enableTune() {
const toggle = this.page.locator(this.l.tuneToggle);
if ((await toggle.getAttribute('aria-checked')) !== 'true') await toggle.click();
}
async setFrequency(minutes) { await this._fillField(this.l.tuneFrequency, minutes); }
async setSilence(minutes) { await this._fillField(this.l.tuneSilence, minutes); }
async confirmLargeBatch() {
const box = this.page.locator(this.l.confirmLarge);
if ((await box.getAttribute('aria-checked')) !== 'true') await box.click();
}
async run() { await this.page.locator(this.l.run).click(); }
async retryFailed() { await this.page.locator(this.l.retry).click(); }
async waitResult(id, status = 'installed', timeout = 30000) {
await expect(this.page.locator(`[data-test="alert-library-install-result-${id}"]`))
.toHaveAttribute('data-status', status, { timeout });
}
async done() {
await this.page.locator(this.l.done).click();
await expect(this.page.locator(this.l.dialog)).toBeHidden({ timeout: 10000 });
}
// ── API verification (read the installed alert back) ──────────────────────
async getInstalledAlert(alertName) {
const base = process.env.ZO_BASE_URL || 'http://localhost:5080';
const org = getOrgIdentifier();
const headers = getAuthHeaders();
// The list endpoint returns a summary; fetch the alert detail for the full
// body (destinations, context_attributes, trigger_condition).
const listResp = await this.page.request.get(`${base}/api/v2/${org}/alerts`, { headers });
if (!listResp.ok()) {
testLogger.warn('alerts list read-back not ok', { status: listResp.status() });
return null;
}
const listBody = await listResp.json().catch(() => null);
const arr = Array.isArray(listBody) ? listBody : listBody?.list || [];
const meta = arr.find((a) => (a.name || a.alert_name) === alertName);
if (!meta) return null;
const id = meta.alert_id || meta.id;
if (!id) return meta;
const detailResp = await this.page.request.get(`${base}/api/v2/${org}/alerts/${id}`, { headers });
if (!detailResp.ok()) return meta;
return (await detailResp.json().catch(() => meta)) || meta;
}
// ── assertions (selectors + expects live here, never in the spec) ─────────
async expectGalleryVisible() { await expect(this.page.locator(this.l.grid)).toBeVisible(); }
async expectCardVisible(id) { await expect(this.card(id)).toBeVisible(); }
async expectCardAbsent(id) { await expect(this.card(id)).toHaveCount(0); }
async expectCardNeedsData(id) { await expect(this.needsDataChip(id)).toBeVisible(); }
async expectCardReady(id) { await expect(this.needsDataChip(id)).toHaveCount(0); }
async expectDrawerPreviewVisible() { await expect(this.page.locator(this.l.drawerPreview)).toBeVisible(); }
async expectDrawerNeedsData() { await expect(this.page.locator(this.l.drawerNeedsData)).toBeVisible(); }
async expectDrawerAvailability() { await expect(this.page.locator(this.l.drawerAvailability)).toBeVisible(); }
async closeDrawer() {
await this.page.keyboard.press('Escape');
await expect(this.page.locator(this.l.drawer)).toBeHidden();
}
async expectEditorAlertName(name) {
await expect(this.page.locator(this.l.editorAlertName).first()).toHaveText(name, { timeout: 15000 });
}
async expectReadyFacetActive() {
await expect(this.page.locator(this.l.statReady)).toHaveAttribute('aria-pressed', 'true');
}
async expectCardActions(id, { customize = true } = {}) {
await expect(this.cardAdd(id)).toBeVisible();
await expect(this.cardCustomize(id)).toHaveCount(customize ? 1 : 0);
}
async expectFolderStep() { await expect(this.page.locator(this.l.folderStep)).toBeVisible(); }
async expectTuneStep() { await expect(this.page.locator(this.l.tuneStep)).toBeVisible(); }
async expectReviewStep() { await expect(this.page.locator(this.l.reviewStep)).toBeVisible(); }
async expectNoDestinationAsked() {
const dialog = this.page.locator(this.l.dialog);
for (const removed of [
'alert-library-install-destination-step',
'alert-library-install-destination',
'alert-library-install-alerts-step',
'alert-library-install-count',
'alert-library-install-select-all',
]) {
await expect(dialog.locator(`[data-test="${removed}"]`)).toHaveCount(0);
}
await expect(dialog.locator('[data-test^="alert-library-install-alert-"]')).toHaveCount(0);
}
async expectRunDisabled() { await expect(this.page.locator(this.l.run)).toBeDisabled(); }
async expectRunEnabled() { await expect(this.page.locator(this.l.run)).toBeEnabled(); }
async expectLargeConfirmVisible() { await expect(this.page.locator(this.l.confirmLarge)).toBeVisible(); }
async expectLargeConfirmAbsent() { await expect(this.page.locator(this.l.confirmLarge)).toHaveCount(0); }
async expectErrorState(label) {
await expect(this.page.locator(this.l.error), label).toBeVisible({ timeout: 20000 });
}
async expectEmptyCatalog() {
await expect(this.page.locator(this.l.emptyCatalog)).toBeVisible({ timeout: 20000 });
}
async clickEmptyCatalogRetry() {
await this.page
.locator(`${this.l.emptyCatalog} [data-test$="-action"], ${this.l.emptyCatalog} button`)
.first()
.click();
}
async expectNoResults() { await expect(this.page.locator(this.l.noResults)).toBeVisible(); }
async expectRailCategoryVisible(id) {
await expect(this.page.locator(`[data-test="alert-library-rail-category-${id}"]`)).toBeVisible();
}
async expectRailPackHeading(pack) {
await expect(this.page.locator(`[data-test="alert-library-rail-pack-heading-${pack}"]`)).toBeVisible();
}
async expectRailCategoryAbsent(id) {
await expect(this.page.locator(`[data-test="alert-library-rail-category-${id}"]`)).toHaveCount(0);
}
async expectPageHeaderVisible() {
await expect(this.page.locator(this.l.page)).toBeVisible();
await expect(this.page.locator(this.l.title)).toBeVisible();
await expect(this.page.locator(this.l.refresh)).toBeVisible();
await expect(this.page.locator(this.l.contribute)).toBeVisible();
}
async expectInstallResultCount(status, count) {
await expect(
this.page.locator(`[data-test^="alert-library-install-result-"][data-status="${status}"]`),
).toHaveCount(count);
}
async expectInstallErrorVisible() {
await expect(this.page.locator('[data-test^="alert-library-install-error-"]').first()).toBeVisible();
}
async expectRetryVisible() { await expect(this.page.locator(this.l.retry)).toBeVisible(); }
// ── internals ─────────────────────────────────────────────────────────────
async _fillField(rootSelector, value) {
// O2 OInput: the data-test is on a wrapper; the real input is the inner node.
const inner = this.page.locator(`${rootSelector} input`);
await inner.waitFor({ state: 'attached', timeout: 10000 });
await inner.fill(String(value), { force: true });
}
}
export default AlertLibraryPage;