test(autoe2e): generated E2E tests for #14046 (alert-list-frequency-retries) (#14061)

Co-authored-by: Shrinath-O2 <214651540+Shrinath-O2@users.noreply.github.com>
This commit is contained in:
Shrinath Rao
2026-09-04 04:05:22 +00:00
committed by GitHub
co-authored by Shrinath-O2
parent 2df66c4494
commit cd0eff459d
3 changed files with 229 additions and 2 deletions
@@ -310,6 +310,19 @@ export class AlertsPage {
alertListTabRealtime: '[data-test="alert-list-tab-realTime"]',
alertListFilterChip: '[data-test="alert-list-filter-chip"]',
// Frequency (cadence) cell — auto-generated by OTable as o2-table-cell-{columnId}
frequencyCell: '[data-test="o2-table-cell-frequency"]',
// Evaluation-history retries cell (AlertEvaluationHistory.vue)
alertDetailsHistoryRetriesCell: '[data-test="alerts-alertevaluationhistory-retries"]',
// Section tab strip (AlertSectionTabs.vue) — header tab switcher
alertSectionTabs: '[data-test="alert-section-tabs"]',
alertSectionTabAlertList: '[data-test="alert-section-tab-alertList"]',
alertSectionTabAlertDestinations: '[data-test="alert-section-tab-alertDestinations"]',
alertSectionTabAlertTemplates: '[data-test="alert-section-tab-alertTemplates"]',
alertSectionTabAlertLibrary: '[data-test="alert-section-tab-alertLibrary"]',
// v3 AddAlert wizard tabs (OToggleGroupItem with data-test="add-alert-tab-{key}")
addAlertTabCondition: '[data-test="add-alert-tab-condition"]',
addAlertTabAdvanced: '[data-test="add-alert-tab-advanced"]',
@@ -1351,6 +1364,105 @@ export class AlertsPage {
}
}
/**
* Click an alert-type filter tab (all / scheduled / realTime) and wait until it is
* actually active (data-state="on") before returning, so callers never assert a
* downstream column against a tab whose click silently failed.
* @param {'all' | 'scheduled' | 'realTime'} value
*/
async clickAlertTypeTab(value) {
const tabs = {
all: this.locators.alertListTabAll,
scheduled: this.locators.alertListTabScheduled,
realTime: this.locators.alertListTabRealtime,
};
const tab = this.page.locator(tabs[value]);
await expect(tab).toBeVisible({ timeout: 10000 });
await tab.click();
await expect(tab).toHaveAttribute('data-state', 'on', { timeout: 10000 });
testLogger.info('Switched alert type tab', { value });
}
/**
* Assert the "Check every" (frequency) cell for the given alert row renders `expected`
* (raw cron string, "N Mins", or "--"). Narrows the list to the alert first so the
* row is present even when the list paginates past it.
* @param {string} name
* @param {string} expected
*/
async expectFrequencyCell(name, expected) {
await this.searchAlert(name);
const row = this.page.locator('tbody tr').filter({ hasText: name }).first();
await expect(row).toBeVisible({ timeout: 15000 });
const cell = row.locator(this.locators.frequencyCell);
await expect(cell).toBeVisible({ timeout: 10000 });
await expect(cell).toHaveText(expected, { timeout: 10000 });
testLogger.info('Frequency cell asserted', { name, expected });
}
/**
* Assert the evaluation-history table renders a "Retries" column with at least one
* numeric retries cell (the column lives between Condition and Evaluation Time).
*/
async expectEvaluationHistoryRetriesColumn() {
const headerText = await this.page
.locator(this.locators.alertDetailsHistoryTable)
.locator('thead')
.innerText();
expect(headerText, 'evaluation history must include a Retries column').toContain('Retries');
const retriesCell = this.page.locator(this.locators.alertDetailsHistoryRetriesCell).first();
await expect(retriesCell).toBeVisible({ timeout: 15000 });
testLogger.info('Evaluation history Retries column and cells present');
}
/**
* Assert the newest evaluation's retries cell renders the given integer (0 for a clean
* self-delivery). Newest-first row ordering means `.first()` is the latest run.
* @param {number} value
*/
async expectEvaluationHistoryRetriesValue(value) {
const cell = this.page.locator(this.locators.alertDetailsHistoryRetriesCell).first();
await expect(cell).toBeVisible({ timeout: 15000 });
await expect(cell).toHaveText(String(value), { timeout: 10000 });
testLogger.info('Evaluation history retries value verified', { value });
}
/**
* Assert the section tab strip renders the given labels left-to-right in order.
* @param {string[]} labels
*/
async expectSectionTabLabels(labels) {
const strip = this.page.locator(this.locators.alertSectionTabs);
await expect(strip).toBeVisible({ timeout: 10000 });
const text = await strip.innerText();
let lastIndex = -1;
for (const label of labels) {
const index = text.indexOf(label);
expect(index, `section tab strip must show "${label}" in order`).toBeGreaterThan(lastIndex);
lastIndex = index;
}
testLogger.info('Section tab labels verified in order', { labels });
}
/**
* Assert a specific section tab renders the exact label text (proves the rename landed:
* "Notification Destinations" / "Alert Library" rather than the deleted short forms).
* @param {'alertList' | 'alertDestinations' | 'alertTemplates' | 'alertLibrary'} key
* @param {string} text
*/
async expectSectionTabLabel(key, text) {
const tabs = {
alertList: this.locators.alertSectionTabAlertList,
alertDestinations: this.locators.alertSectionTabAlertDestinations,
alertTemplates: this.locators.alertSectionTabAlertTemplates,
alertLibrary: this.locators.alertSectionTabAlertLibrary,
};
const tab = this.page.locator(tabs[key]);
await expect(tab).toBeVisible({ timeout: 10000 });
await expect(tab).toHaveText(text, { timeout: 10000 });
testLogger.info('Section tab label verified', { key, text });
}
/**
* Open the Advanced-tab template override OSelect dropdown.
*/
@@ -30,7 +30,10 @@ const testLogger = require('../utils/test-logger.js');
const PageManager = require('../../pages/page-manager.js');
const logData = require('../../fixtures/log.json');
const { getOrgIdentifier } = require('../utils/cloud-auth.js');
const { findAlertId, getAlert, deleteAlerts, seedAlertFixtures, DEST } = require('../utils/alerts-api-helpers.js');
const {
findAlertId, getAlert, deleteAlerts, seedAlertFixtures, DEST,
simpleAlert, realtimeAlert, cronAlert, createAlert, waitForAlertOutcome,
} = require('../utils/alerts-api-helpers.js');
const NETWORK_IDLE_TIMEOUT_MS = 30000;
const TEST_STREAM = 'e2e_automate';
@@ -176,4 +179,104 @@ test.describe('Alerts — priority, tags & additional variables', {
});
testLogger.info('Alert list column set verified');
});
// ─────────────────────────────────────────────────────────────────────────
// Frequency ("Check every") cadence cell + column conditional (issue #14038)
// ─────────────────────────────────────────────────────────────────────────
test('frequency cell renders the cadence for cron, minute-interval and real-time alerts', {
tag: ['@alert-list-frequency-retries', '@all', '@alerts', '@P0'],
}, async ({ page }) => {
const suffix = Math.random().toString(36).substring(2, 8);
const cronName = 'auto_freq_cron_' + suffix;
const minsName = 'auto_freq_mins_' + suffix;
const rtName = 'auto_freq_rt_' + suffix;
createdAlertNames.push(cronName, minsName, rtName);
const cronResp = await createAlert(page, cronAlert(cronName));
const minsResp = await createAlert(page, simpleAlert(minsName));
const rtResp = await createAlert(page, realtimeAlert(rtName));
expect(cronResp.ok(), `cron alert create failed: ${cronResp.status()}`).toBeTruthy();
expect(minsResp.ok(), `minutes alert create failed: ${minsResp.status()}`).toBeTruthy();
expect(rtResp.ok(), `realtime alert create failed: ${rtResp.status()}`).toBeTruthy();
await page.goto(`${logData.alertUrl}?org_identifier=${getOrgIdentifier()}`);
await page.waitForLoadState('networkidle', { timeout: NETWORK_IDLE_TIMEOUT_MS }).catch(() => {});
await pm.alertsPage.expectFrequencyCell(cronName, '0 */10 * * * *');
await pm.alertsPage.expectFrequencyCell(minsName, '10 Mins');
await pm.alertsPage.expectFrequencyCell(rtName, '--');
testLogger.info('Frequency cells render cron, minutes and realtime cadence correctly');
});
test('Check every column is present on scheduled and absent on the realTime tab', {
tag: ['@alert-list-frequency-retries', '@all', '@alerts', '@P0'],
}, async ({ page }) => {
const suffix = Math.random().toString(36).substring(2, 8);
const schedName = 'auto_tab_sched_' + suffix;
const rtName = 'auto_tab_rt_' + suffix;
createdAlertNames.push(schedName, rtName);
// One scheduled + one real-time alert so both tabs render a table header to assert against.
await createAlert(page, simpleAlert(schedName));
await createAlert(page, realtimeAlert(rtName));
await page.goto(`${logData.alertUrl}?org_identifier=${getOrgIdentifier()}`);
await page.waitForLoadState('networkidle', { timeout: NETWORK_IDLE_TIMEOUT_MS }).catch(() => {});
await pm.alertsPage.clickAlertTypeTab('scheduled');
await pm.alertsPage.expectListColumns({ present: ['Check every'], absent: [] });
await pm.alertsPage.clickAlertTypeTab('realTime');
await pm.alertsPage.expectListColumns({ present: [], absent: ['Check every'] });
testLogger.info('Check every column present on scheduled, absent on realTime');
});
// ─────────────────────────────────────────────────────────────────────────
// Retries column in the per-alert evaluation history
// ─────────────────────────────────────────────────────────────────────────
test('evaluation history shows a Retries column with a numeric value', {
tag: ['@alert-list-frequency-retries', '@all', '@alerts', '@P1'],
}, async ({ page }) => {
const suffix = Math.random().toString(36).substring(2, 8);
const retryName = 'auto_retry_' + suffix;
createdAlertNames.push(retryName);
// Fast cadence so the scheduler evaluates it within the waitForAlertOutcome window.
const retryAlert = simpleAlert(retryName);
retryAlert.trigger_condition.frequency = 1;
const createResp = await createAlert(page, retryAlert);
expect(createResp.ok(), `alert create failed: ${createResp.status()}`).toBeTruthy();
// Guarantee a recent evaluation so the history endpoint returns a hit.
const evaluated = await waitForAlertOutcome(page, retryName);
expect(evaluated, `scheduled alert ${retryName} must be evaluated within the timeout`).toBeTruthy();
expect(evaluated.last_outcome, 'the alert must have a run outcome').toBeTruthy();
const id = await findAlertId(page, retryName);
expect(id, 'the evaluated alert must be findable by name').toBeTruthy();
await pm.alertDetailPage.open(id);
await pm.alertsPage.expectAlertDetailsHistorySectionVisible();
await pm.alertsPage.expectEvaluationHistoryRetriesColumn();
await pm.alertsPage.expectEvaluationHistoryRetriesValue(0);
testLogger.info('Retries column renders a numeric value in evaluation history');
});
// ─────────────────────────────────────────────────────────────────────────
// Section tab strip agrees with the nav rail
// ─────────────────────────────────────────────────────────────────────────
test('the alert section tab strip shows the renamed labels', {
tag: ['@alert-list-frequency-retries', '@all', '@alerts', '@P1'],
}, async ({ page }) => {
await page.goto(`${logData.alertUrl}?org_identifier=${getOrgIdentifier()}`);
await page.waitForLoadState('networkidle', { timeout: NETWORK_IDLE_TIMEOUT_MS }).catch(() => {});
await pm.alertsPage.expectSectionTabLabels([
'All Alerts', 'Notification Destinations', 'Destination Templates', 'Alert Library',
]);
await pm.alertsPage.expectSectionTabLabel('alertDestinations', 'Notification Destinations');
await pm.alertsPage.expectSectionTabLabel('alertLibrary', 'Alert Library');
testLogger.info('Section tab strip shows the renamed labels');
});
});
@@ -81,6 +81,18 @@ function realtimeAlert(name) {
return a;
}
/** A scheduled cron alert whose cadence cell renders the raw cron string verbatim. */
function cronAlert(name) {
const a = simpleAlert(name);
a.trigger_condition = {
...a.trigger_condition,
frequency_type: 'cron',
frequency: 1,
cron: '0 */10 * * * *',
};
return a;
}
/** V1 composite payload. Operands are stable alert IDs, never display names. */
function compositeAlert(name, childIds, overrides = {}) {
return {
@@ -228,7 +240,7 @@ function isFiringOutcome(outcome) {
module.exports = {
BASE, STREAM, SINK, TMPL, DEST,
uniq, urls, api,
simpleAlert, multiAlert, groupedSimpleAlert, realtimeAlert,
simpleAlert, multiAlert, groupedSimpleAlert, realtimeAlert, cronAlert,
compositeAlert, validateComposite, getCompositeReferences,
createAlert, listAlerts, findAlertId, getAlert, deleteAlerts, seedAlertFixtures,
ingest, getAlertGroups, getAlertTransitions,