mirror of
https://github.com/openobserve/openobserve.git
synced 2026-11-04 00:08:55 +00:00
## What & why Sentinel (Council of Agents) audit-driven **Page Object Model compliance** across the Playwright suite. Many specs (a lot added recently, some AI-generated) had raw `page.locator(...)` / `page.getBy*(...)` selectors directly in spec files — including inside `expect(...)` — which the Sentinel flags as CRITICAL. This PR relocates those selectors into their page objects and calls page-object methods/getters instead. A full audit of **all 256 specs** was generated first (stored locally under `tests/ui-testing/MD_Files/sentinel-audit-2026-08-10/`, which is gitignored). Original totals: **1,609** raw selectors, **190** inside assertions, plus console/testLogger items. ## Scope of THIS PR (non-Dashboards) Every non-Dashboards spec is now **100% raw-selector clean** (verified: 0 occurrences). Shards covered: - **GeneralTests, Logs (Core/Builder-Advanced/Features/Regression), Traces (+Reg), Streams (+Reg), Metrics (+Reg), Pipelines, Reports (+Reg), Anomaly, Login/auth, auth-warm, and the unmatched Alerts specs.** - `console.*` → `testLogger` where it was Node-context (kept `console.*` **inside `page.evaluate()`** — that's browser context; `testLogger` doesn't exist there). - `testLogger` import/use added where missing. - **`waitForTimeout` intentionally left as-is** (logged in the audit, not rewritten — per direction). ## Deferred to a follow-up PR The **heavy Dashboards shards** (Variables 693, Config-Settings 255, Tables 127, Charts 81, Core 79, Visualize 65, Settings 52, Panel-DateTime 55, Dashboard-Regression 52 raw selectors) are **not** in this PR — they're the bulk of the work and will be a separate, focused pass. (dashboard-streaming console cleanup is included here.) ## Verification Fixes were applied and executed **shard by shard** against a local env, gated green before commit where the env allowed: | Group | Local result | |-------|--------------| | GeneralTests (edited specs) | 52 passed / 11 skipped / 0 failed | | Logs (shareLink, logsQuickPick, indexquery) | 21 passed | | Traces / Streams / Metrics | 49 passed / 2 skipped / 0 failed | | Pipelines | 56 passed / 17 skipped / 0 failed | | logs-bugs :548, dashboard-streaming | verified not regressed (isolation) | **Alerts create-wizard specs** (Alerts shard, Alerts-Regression, unmatched Alerts) could not be gated locally — the alert-creation wizard's stream popover times out on this env **on unmodified code too** (reproduced on original, test `:363`), so those refactors are 1:1 selector→method mappings, syntax-clean (`node --check`), and rely on CI to gate. Every changed `.js` passes `node --check`. `waitForTimeout` unchanged; no behavior changes intended. ## Stats 51 files changed. Selectors moved into page objects, not deleted.
176 lines
7.8 KiB
JavaScript
176 lines
7.8 KiB
JavaScript
// Copyright 2026 OpenObserve Inc.
|
|
|
|
const testLogger = require('../../playwright-tests/utils/test-logger.js');
|
|
|
|
export class StreamsFormValidationPage {
|
|
/**
|
|
* @param {import('@playwright/test').Page} page
|
|
*/
|
|
constructor(page) {
|
|
this.page = page;
|
|
|
|
// ── Navigation ───────────────────────────────────────────────────────
|
|
this.streamsMenuLink = '[data-test="menu-link-\\/streams-item"]';
|
|
this.addStreamButton = '[data-test="log-stream-add-stream-btn"]';
|
|
|
|
// ── Add Stream dialog ─────────────────────────────────────────────────
|
|
// ODialog wrapper
|
|
this.addStreamDialog = '[data-test="add-stream-dialog"]';
|
|
|
|
// OInput: data-test="add-stream-name-input" → -field (native input) and -error
|
|
this.nameField = '[data-test="add-stream-name-input-field"]';
|
|
this.nameError = '[data-test="add-stream-name-input-error"]';
|
|
|
|
// OSelect: data-test="add-stream-type-input" → -trigger (opens dropdown), -popover (content), -error
|
|
this.typeTrigger = '[data-test="add-stream-type-input-trigger"]';
|
|
this.typePopover = '[data-test="add-stream-type-input-popover"]';
|
|
this.typeError = '[data-test="add-stream-type-input-error"]';
|
|
|
|
// Data retention wrapper div (conditionally rendered)
|
|
this.dataRetentionWrapper = '[data-test="add-stream-data-retention-input"]';
|
|
// OInput data-test="add-stream-data-retention" → -field (native input) and -error
|
|
this.dataRetentionField = '[data-test="add-stream-data-retention-field"]';
|
|
this.dataRetentionError = '[data-test="add-stream-data-retention-error"]';
|
|
|
|
// ODialog built-in primary / secondary buttons scoped to dialog
|
|
this.saveBtn = '[data-test="add-stream-dialog"] [data-test="o-dialog-primary-btn"]';
|
|
this.cancelBtn = '[data-test="add-stream-dialog"] [data-test="o-dialog-secondary-btn"]';
|
|
|
|
// OSelect data type picker for a stream field
|
|
this.fieldDataTypeSelect = '[data-test="add-stream-field-data-type-select"]';
|
|
|
|
// Toast selectors
|
|
this.toastSuccess = '[data-test-variant="success"]';
|
|
}
|
|
|
|
// ── Navigation ────────────────────────────────────────────────────────────
|
|
|
|
async navigateToStreamsPage() {
|
|
testLogger.info('Navigating to Streams explorer page');
|
|
// Navigate by URL instead of clicking the left-nav tile: post-revamp the
|
|
// tile is a hover-flyout group and a plain click from the home page can
|
|
// land without the streams list (seen on cloud). The org_identifier
|
|
// param keeps the app bound to the test org.
|
|
await this.page.goto(`${process.env.ZO_BASE_URL}/web/streams?org_identifier=${process.env.ORGNAME}`);
|
|
await this.page.waitForLoadState('domcontentloaded');
|
|
await this.page.waitForLoadState('networkidle', { timeout: 30000 }).catch(() => {});
|
|
}
|
|
|
|
async openAddStreamDialog() {
|
|
testLogger.info('Opening Add Stream dialog');
|
|
// The Add Stream button renders only after zoConfig loads
|
|
// (v-if="isSchemaUDSEnabled"), so wait for it rather than click-timeout.
|
|
await this.page.locator(this.addStreamButton).waitFor({ state: 'visible', timeout: 30000 });
|
|
await this.page.locator(this.addStreamButton).click();
|
|
await this.page.locator(this.addStreamDialog).waitFor({ state: 'visible', timeout: 10000 });
|
|
}
|
|
|
|
// ── Form interactions ─────────────────────────────────────────────────────
|
|
|
|
async fillStreamName(name) {
|
|
testLogger.info(`Filling stream name: "${name}"`);
|
|
await this.page.locator(this.nameField).fill(name);
|
|
}
|
|
|
|
async clearStreamName() {
|
|
testLogger.info('Clearing stream name field');
|
|
await this.page.locator(this.nameField).fill('');
|
|
}
|
|
|
|
async selectStreamType(typeLabel) {
|
|
testLogger.info(`Selecting stream type: "${typeLabel}"`);
|
|
// Click the trigger to open the dropdown
|
|
await this.page.locator(this.typeTrigger).click();
|
|
// Wait for popover to open, then click the matching option
|
|
await this.page.locator(this.typePopover).waitFor({ state: 'visible', timeout: 5000 });
|
|
await this.page.getByRole('option', { name: typeLabel, exact: true }).click();
|
|
}
|
|
|
|
async fillDataRetention(value) {
|
|
testLogger.info(`Filling data retention: ${value}`);
|
|
// The retention field is conditionally rendered — wait for it before filling
|
|
await this.page.locator(this.dataRetentionField).waitFor({ state: 'visible', timeout: 5000 });
|
|
await this.page.locator(this.dataRetentionField).fill(String(value));
|
|
}
|
|
|
|
async clickSave() {
|
|
testLogger.info('Clicking Save button');
|
|
await this.page.locator(this.saveBtn).click();
|
|
}
|
|
|
|
async clickCancel() {
|
|
testLogger.info('Clicking Cancel button');
|
|
await this.page.locator(this.cancelBtn).click();
|
|
}
|
|
|
|
// ── StreamFieldInputs interactions ────────────────────────────────────────
|
|
|
|
async clickAddFieldBtn() {
|
|
testLogger.info('Clicking Add Field button');
|
|
await this.page.locator('[data-test="add-stream-add-field-btn"]').first().click();
|
|
}
|
|
|
|
async fillFieldName(index, value) {
|
|
testLogger.info(`Filling field name at index ${index}: "${value}"`);
|
|
const row = this.page.locator(`[data-test="add-stream-field-row-${index}"]`);
|
|
const input = row.locator('[data-test="add-stream-field-name-input"] input');
|
|
await input.fill(value);
|
|
await input.dispatchEvent('input');
|
|
}
|
|
|
|
async clickDeleteFieldBtn(index) {
|
|
testLogger.info(`Clicking delete field button at row ${index}`);
|
|
const row = this.page.locator(`[data-test="add-stream-field-row-${index}"]`);
|
|
await row.locator('[data-test="add-stream-delete-field-btn"]').click();
|
|
}
|
|
|
|
getFieldRowLocator(index) {
|
|
return this.page.locator(`[data-test="add-stream-field-row-${index}"]`);
|
|
}
|
|
|
|
getFieldNameErrorLocator(index) {
|
|
// OInput has data-test="add-stream-field-name-input-{index}" → generates -error span
|
|
return this.page.locator(`[data-test="add-stream-field-name-input-${index}-error"]`);
|
|
}
|
|
|
|
getFieldDataTypeErrorLocator(index) {
|
|
const row = this.page.locator(`[data-test="add-stream-field-row-${index}"]`);
|
|
// OSelect auto-generates ${parentDataTest}-error from data-test="add-stream-field-data-type-select"
|
|
return row.locator('[data-test="add-stream-field-data-type-select-error"]');
|
|
}
|
|
|
|
// ── Assertions ────────────────────────────────────────────────────────────
|
|
|
|
getDialogLocator() {
|
|
return this.page.locator(this.addStreamDialog);
|
|
}
|
|
|
|
getFieldDataTypeSelectLocator() {
|
|
return this.page.locator(this.fieldDataTypeSelect);
|
|
}
|
|
|
|
getNameErrorLocator() {
|
|
return this.page.locator(this.nameError);
|
|
}
|
|
|
|
getTypeErrorLocator() {
|
|
return this.page.locator(this.typeError);
|
|
}
|
|
|
|
getDataRetentionErrorLocator() {
|
|
return this.page.locator(this.dataRetentionError);
|
|
}
|
|
|
|
getSaveBtnLocator() {
|
|
return this.page.locator(this.saveBtn);
|
|
}
|
|
|
|
getCancelBtnLocator() {
|
|
return this.page.locator(this.cancelBtn);
|
|
}
|
|
|
|
getDataRetentionWrapperLocator() {
|
|
return this.page.locator(this.dataRetentionWrapper);
|
|
}
|
|
}
|