mirror of
https://github.com/openobserve/openobserve.git
synced 2026-11-06 08:09:24 +00:00
### **PR Type** Enhancement, Tests ___ ### **Description** - Add robust alerts cleanup utilities - Improve UI flows and resiliency - Support deployed env testing delays - Add CI cleanup and health checks ___ ### Diagram Walkthrough ```mermaid flowchart LR UI["Playwright UI flows hardened"] API["APICleanup cascade (Alerts→Folders→Destinations→Templates)"] CI["CI workflow targets deployed env"] Delay["Configurable test step delay"] Data["Global ingestion more robust"] UI -- "uses" --> API CI -- "runs cleanup.spec" --> API CI -- "health checks + env vars" --> Data Delay -- "slows page ops" --> UI Data -- "ingest logs with parsing" --> UI ``` <details> <summary><h3> File Walkthrough</h3></summary> <table><thead><tr><th></th><th align="left">Relevant files</th></tr></thead><tbody><tr><td><strong>Enhancement</strong></td><td><details><summary>7 files</summary><table> <tr> <td><strong>alertDestinationsPage.js</strong><dd><code>Add search, cascade-safe delete, bulk prefix cleanup</code> </dd></td> <td><a href="https://github.com/openobserve/openobserve/pull/8839/files#diff-57e5c53b4ef998b3d8708149620ff94dcc46807927f1a3a9e9f28ed3ebe25b0a">+178/-0</a> </td> </tr> <tr> <td><strong>alertTemplatesPage.js</strong><dd><code>Add template search, in-use handling, prefix cleanup</code> </dd></td> <td><a href="https://github.com/openobserve/openobserve/pull/8839/files#diff-f4ebd248b2140051e74dbbbd1aabe344f01c80704ce5857e9c28a04350e018f3">+177/-2</a> </td> </tr> <tr> <td><strong>alertsPage.js</strong><dd><code>Harden alert actions, bulk deletion, search across folders</code></dd></td> <td><a href="https://github.com/openobserve/openobserve/pull/8839/files#diff-79ee01d6d7f145f19207b93b87143b545a1188cfc6a82bc8c45fecfbf68f8a1d">+280/-9</a> </td> </tr> <tr> <td><strong>apiCleanup.js</strong><dd><code>New API-based cascade cleanup utility</code> </dd></td> <td><a href="https://github.com/openobserve/openobserve/pull/8839/files#diff-537fe76c0d8e26e81c5f77c83bd0ee35daaa973822105c809bf83171a26b33c5">+399/-0</a> </td> </tr> <tr> <td><strong>page-manager.js</strong><dd><code>Wire APICleanup into PageManager</code> </dd></td> <td><a href="https://github.com/openobserve/openobserve/pull/8839/files#diff-565eab8b3c767ae50c9d05e591621d14f66b81af84d6b5ea6efe136c1c3933df">+4/-0</a> </td> </tr> <tr> <td><strong>baseFixtures.js</strong><dd><code>Add configurable test step delay hooks</code> </dd></td> <td><a href="https://github.com/openobserve/openobserve/pull/8839/files#diff-c4c5ee7fbb248d1ab14d3d4e33ffb9626f9aab74c9e0207fcfc08051c8c3c7ee">+46/-0</a> </td> </tr> <tr> <td><strong>global-setup.js</strong><dd><code>Harden login and ingestion with diagnostics</code> </dd></td> <td><a href="https://github.com/openobserve/openobserve/pull/8839/files#diff-06323dad7214f86b41b62e121e716eeea0054bc4d950c6bcf4a0efe53894acc8">+61/-22</a> </td> </tr> </table></details></td></tr><tr><td><strong>Bug fix</strong></td><td><details><summary>1 files</summary><table> <tr> <td><strong>commonActions.js</strong><dd><code>Fix ingestion URLs to use org variable</code> </dd></td> <td><a href="https://github.com/openobserve/openobserve/pull/8839/files#diff-4d726dd2bdb34b2c90a31798ff49b6df5bc952bd049b7c38bebb18b6db0076e2">+4/-4</a> </td> </tr> </table></details></td></tr><tr><td><strong>Tests</strong></td><td><details><summary>4 files</summary><table> <tr> <td><strong>alerts-e2e-flow.spec.js</strong><dd><code>Pre-move folder cleanup and stabilization</code> </dd></td> <td><a href="https://github.com/openobserve/openobserve/pull/8839/files#diff-32972e38c1fe0e8bb887f9f254b3130916a36f96ea924f9987493d44efefeab9">+13/-3</a> </td> </tr> <tr> <td><strong>alerts-import.spec.js</strong><dd><code>Use dedicated invalid import test data</code> </dd></td> <td><a href="https://github.com/openobserve/openobserve/pull/8839/files#diff-62b6ec4818685eacc0a934d68aaadfd73b512ef4f4be25fcd5135b97c2844912">+1/-1</a> </td> </tr> <tr> <td><strong>cleanup.spec.js</strong><dd><code>New pre-test API cleanup spec</code> </dd></td> <td><a href="https://github.com/openobserve/openobserve/pull/8839/files#diff-c214275a512fa285a7e4f2ab61dfcb42d71a0436b0e4241f94d7b45693587983">+29/-0</a> </td> </tr> <tr> <td><strong>invalid-alert.json</strong><dd><code>Add invalid alert sample for import tests</code> </dd></td> <td><a href="https://github.com/openobserve/openobserve/pull/8839/files#diff-c695f71048e9393a45d3e3e3e415696559fecf3ba85712e7c3f366092f53f424">+24/-0</a> </td> </tr> </table></details></td></tr><tr><td><strong>Configuration changes</strong></td><td><details><summary>1 files</summary><table> <tr> <td><strong>playwright.yml</strong><dd><code>Target deployed env; add cleanup job and health checks</code> </dd></td> <td><a href="https://github.com/openobserve/openobserve/pull/8839/files#diff-7afcd2d8f7b49bda74843f209eefb7b2da45f7e7803bf2e4bd636699b76aa2d3">+123/-65</a></td> </tr> </table></details></td></tr></tr></tbody></table> </details> ___
197 lines
8.7 KiB
JavaScript
197 lines
8.7 KiB
JavaScript
import { exec } from 'child_process';
|
|
import { test } from '@playwright/test';
|
|
import { expect } from '@playwright/test';
|
|
|
|
// Common Locator exports
|
|
export var dateTimeButtonLocator='[data-test="date-time-btn"]';
|
|
export var relative30SecondsButtonLocator='[data-test="date-time-relative-30-s-btn"]';
|
|
export var absoluteTabLocator='[data-test="date-time-absolute-tab"]';
|
|
export var Past30SecondsValue='Past 30 Seconds';
|
|
export var startTimeValue='01:01:01';
|
|
export var endTimeValue='02:02:02';
|
|
export var startDateTimeValue='2024/10/01 01:01:01';
|
|
export var endDateTimeValue='2024/10/01 02:02:02';
|
|
|
|
export class CommonActions {
|
|
constructor(page) {
|
|
this.page = page;
|
|
|
|
// Navigation locators
|
|
this.alertsMenuItem = '[data-test="menu-link-\\/alerts-item"]';
|
|
this.settingsMenuItem = '[data-test="menu-link-settings-item"]';
|
|
this.homeMenuItem = '[data-test="menu-link-\\/-item"]';
|
|
}
|
|
|
|
async navigateToAlerts() {
|
|
await this.page.locator(this.alertsMenuItem).click();
|
|
await this.page.waitForTimeout(2000);
|
|
await expect(this.page.locator('[data-test="alerts-list-title"]')).toContainText('Alerts');
|
|
}
|
|
|
|
async navigateToSettings() {
|
|
await this.page.locator(this.settingsMenuItem).click();
|
|
await this.page.waitForTimeout(2000);
|
|
}
|
|
|
|
async navigateToHome() {
|
|
await this.page.locator(this.homeMenuItem).click();
|
|
await this.page.waitForTimeout(2000);
|
|
}
|
|
|
|
/**
|
|
* Helper method to scroll through a dropdown to find and click an option
|
|
* @param {string} optionName - Name of the option to find
|
|
* @param {string} optionType - Type of option ('template' or 'folder')
|
|
* @returns {Promise<boolean>} - Whether the option was found
|
|
*/
|
|
async scrollAndFindOption(optionName, optionType) {
|
|
// Use more specific locator to target the visible dropdown to avoid strict mode violation
|
|
const dropdown = this.page.locator('.q-menu:visible').first();
|
|
let optionFound = false;
|
|
let maxScrolls = 50;
|
|
let scrollAmount = 1000;
|
|
let totalScrolled = 0;
|
|
|
|
while (!optionFound && maxScrolls > 0) {
|
|
try {
|
|
// Try to find and click the option
|
|
const option = optionType === 'template'
|
|
? this.page.getByText(optionName, { exact: true })
|
|
: this.page.getByRole('option', { name: optionName });
|
|
|
|
if (await option.isVisible()) {
|
|
if (optionType === 'template') {
|
|
await option.click();
|
|
} else {
|
|
await option.locator('span').click();
|
|
}
|
|
optionFound = true;
|
|
console.log(`Found ${optionType} after scrolling: ${optionName}`);
|
|
await this.page.waitForTimeout(500);
|
|
} else {
|
|
// Get the current scroll position and height
|
|
const { scrollTop, scrollHeight, clientHeight } = await dropdown.evaluate(el => ({
|
|
scrollTop: el.scrollTop,
|
|
scrollHeight: el.scrollHeight,
|
|
clientHeight: el.clientHeight
|
|
}));
|
|
|
|
// If we've scrolled to the bottom, start from the top again
|
|
if (scrollTop + clientHeight >= scrollHeight) {
|
|
await dropdown.evaluate(el => el.scrollTop = 0);
|
|
totalScrolled = 0;
|
|
await this.page.waitForTimeout(500);
|
|
} else {
|
|
// Scroll down
|
|
await dropdown.evaluate((el, amount) => el.scrollTop += amount, scrollAmount);
|
|
totalScrolled += scrollAmount;
|
|
await this.page.waitForTimeout(500);
|
|
}
|
|
maxScrolls--;
|
|
}
|
|
} catch (error) {
|
|
// If option not found, scroll and try again
|
|
await dropdown.evaluate((el, amount) => el.scrollTop += amount, scrollAmount);
|
|
totalScrolled += scrollAmount;
|
|
await this.page.waitForLoadState('networkidle');
|
|
maxScrolls--;
|
|
}
|
|
}
|
|
|
|
if (!optionFound) {
|
|
console.error(`Failed to find ${optionType} ${optionName} after scrolling ${totalScrolled}px`);
|
|
throw new Error(`${optionType} ${optionName} not found in dropdown after scrolling`);
|
|
}
|
|
|
|
return optionFound;
|
|
}
|
|
|
|
async ingestTestData(streamName) {
|
|
const curlCommand = `curl -u ${process.env["ZO_ROOT_USER_EMAIL"]}:${process.env["ZO_ROOT_USER_PASSWORD"]} -k ${process.env["ZO_BASE_URL"]}/api/${process.env["ORGNAME"]}/${streamName}/_json -d '[{"level":"info","job":"test","log":"test message for openobserve. this data ingestion has been done using a playwright automation script."}]'`;
|
|
|
|
return new Promise((resolve, reject) => {
|
|
exec(curlCommand, (error, stdout, stderr) => {
|
|
if (error) {
|
|
console.error('Error executing curl command:', error);
|
|
reject(error);
|
|
return;
|
|
}
|
|
console.log('Curl command response:', stdout);
|
|
console.log('Successfully ingested test data');
|
|
resolve();
|
|
});
|
|
});
|
|
}
|
|
|
|
async ingestCustomTestData(streamName) {
|
|
const curlCommand = `curl -u ${process.env["ZO_ROOT_USER_EMAIL"]}:${process.env["ZO_ROOT_USER_PASSWORD"]} -k ${process.env["ZO_BASE_URL"]}/api/${process.env["ORGNAME"]}/${streamName}/_json -d @utils/td150.json`;
|
|
|
|
return new Promise((resolve, reject) => {
|
|
exec(curlCommand, (error, stdout, stderr) => {
|
|
if (error) {
|
|
console.error('Error executing curl command:', error);
|
|
reject(error);
|
|
return;
|
|
}
|
|
console.log('Custom curl command response:', stdout);
|
|
console.log('Successfully ingested custom test data');
|
|
resolve();
|
|
});
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Helper method to conditionally ingest test data based on test title
|
|
* Skips data ingestion for scheduled alert tests
|
|
* @param {string} testTitle - The title of the current test
|
|
* @param {string} streamName - The stream name for data ingestion
|
|
*/
|
|
async skipDataIngestionForScheduledAlert(testTitle, streamName = 'auto_playwright_stream') {
|
|
// Skip data ingestion for scheduled alert test
|
|
if (!testTitle.includes('Scheduled Alert')) {
|
|
// Ingest test data using common actions
|
|
await this.ingestTestData(streamName);
|
|
}
|
|
}
|
|
|
|
// Dismiss any blocking overlays/modals that might intercept clicks
|
|
async dismissBlockingOverlays() {
|
|
for (let i = 0; i < 3; i += 1) {
|
|
const backdrop = this.page.locator('.q-dialog__backdrop');
|
|
const count = await backdrop.count();
|
|
const visible = count > 0 ? await backdrop.first().isVisible().catch(() => false) : false;
|
|
if (!visible) break;
|
|
await this.page.keyboard.press('Escape').catch(() => {});
|
|
await backdrop.first().waitFor({ state: 'hidden', timeout: 1000 }).catch(() => {});
|
|
await this.page.waitForTimeout(150);
|
|
}
|
|
}
|
|
|
|
// Return boolean streaming state from Organization settings toggle
|
|
async getStreamingState() {
|
|
const root = this.page.locator('[data-test="general-settings-enable-streaming"]').first();
|
|
await root.waitFor({ state: 'visible', timeout: 10000 });
|
|
const ariaChecked = await root.getAttribute('aria-checked');
|
|
return ariaChecked === 'true';
|
|
}
|
|
|
|
// Flip streaming toggle, save, wait for toast, reload, and verify state
|
|
async flipStreaming() {
|
|
await this.dismissBlockingOverlays();
|
|
try {
|
|
await this.page.locator('[data-test="menu-link-settings-item"]').click();
|
|
} catch {
|
|
await this.page.reload().catch(() => {});
|
|
await this.page.waitForTimeout(300);
|
|
await this.dismissBlockingOverlays();
|
|
await this.page.locator('[data-test="menu-link-settings-item"]').click();
|
|
}
|
|
const isOn = await this.getStreamingState();
|
|
await this.page.locator('[data-test="general-settings-enable-streaming"] div').nth(2).click();
|
|
await this.page.locator('[data-test="dashboard-add-submit"]').click();
|
|
await this.page.getByText('Organization settings updated', { exact: false }).waitFor({ timeout: 15000 });
|
|
await this.page.reload();
|
|
const newState = await this.getStreamingState();
|
|
console.log(`[Streaming Toggle] ${isOn ? 'ON' : 'OFF'} -> ${newState ? 'ON' : 'OFF'}`);
|
|
}
|
|
}
|