Files
openobserve/tests/ui-testing/pages/generalPages/cipherKeysFormValidationPage.js
T
ktx-mihirandAbhay Padamani 75b824f29a fix: cipherkeys module form migration with zod (#12971)
- form migration with zod schema for cipher keys module

---------

Co-authored-by: Abhay Padamani <141122205+ktx-abhay@users.noreply.github.com>
2026-07-15 15:04:32 +00:00

291 lines
13 KiB
JavaScript

// Copyright 2026 OpenObserve Inc.
import { expect } from '@playwright/test';
import { openOSelectDropdown } from '../alertsPages/oselectHelpers.js';
export class CipherKeysFormValidationPage {
constructor(page) {
this.page = page;
// Navigation
this.settingsMenu = page.locator('[data-test="menu-link-/settings-item"]');
this.cipherKeyTab = page.locator('[data-test="management-cipher-key-tab"]');
this.addCipherKeyButton = page.locator('[data-test="cipher-keys-add-btn"]');
// AddCipherKey — name field
this.nameInput = page.locator('[data-test="add-cipher-key-name-input-field"]');
this.nameError = page.locator('[data-test="add-cipher-key-name-input-error"]');
// AddCipherKey — store type select
this.storeTypeSelect = page.locator('[data-test="add-cipher-key-type-input"]');
this.storeTypeError = page.locator('[data-test="add-cipher-key-type-input-error"]');
this.storeTypeAkeylessOption = page.locator('[data-test="add-cipher-key-type-input-option"][data-test-value="akeyless"]');
// AddCipherKey — step buttons
this.continueButton = page.locator('[data-test="add-report-step1-continue-btn"]');
this.saveButton = page.locator('[data-test="add-cipher-key-save-btn"]');
this.cancelButton = page.locator('[data-test="add-cipher-key-cancel-btn"]');
this.backButton = page.locator('[data-test="add-cipher-key-step2-back-btn"]');
// AddOpenobserveType — secret textarea
this.secretInput = page.locator('[data-test="add-cipher-key-openobserve-secret-input-field"]');
this.secretError = page.locator('[data-test="add-cipher-key-openobserve-secret-input-error"]');
// AddAkeylessType — base URL
this.akeylessBaseUrlInput = page.locator('[data-test="add-cipher-key-akeyless-baseurl-input-field"]');
this.akeylessBaseUrlError = page.locator('[data-test="add-cipher-key-akeyless-baseurl-input-error"]');
// AddAkeylessType — access ID
this.akeylessAccessIdInput = page.locator('[data-test="add-cipher-key-akeyless-access-id-input-field"]');
this.akeylessAccessIdError = page.locator('[data-test="add-cipher-key-akeyless-access-id-input-error"]');
// AddAkeylessType — auth method select (akeyless step 1 sub-form).
// NOTE: shares the data-test "add-cipher-key-auth-method-input" with the
// AddEncryptionMechanism provider-type select, but the two are never mounted
// at the same time (akeyless = step 1 sub-form, mechanism = step 2).
this.authMethodSelect = page.locator('[data-test="add-cipher-key-auth-method-input"]');
this.authMethodError = page.locator('[data-test="add-cipher-key-auth-method-input-error"]');
// AddAkeylessType — access key
this.akeylessAccessKeyInput = page.locator('[data-test="add-cipher-key-akeyless-access-key-input-field"]');
this.akeylessAccessKeyError = page.locator('[data-test="add-cipher-key-akeyless-access-key-input-error"]');
// AddAkeylessType — secret type select
this.secretTypeSelect = page.locator('[data-test="add-cipher-key-secret-type-input"]');
this.secretTypeError = page.locator('[data-test="add-cipher-key-secret-type-input-error"]');
// AddAkeylessType — static secret name
this.staticSecretNameInput = page.locator('[data-test="add-cipher-key-akeyless-static-secret-name-input-field"]');
this.staticSecretNameError = page.locator('[data-test="add-cipher-key-akeyless-static-secret-name-input-error"]');
// AddEncryptionMechanism — provider type select and algorithm select (step 2)
this.encryptionProviderTypeSelect = page.locator('[data-test="add-cipher-key-auth-method-input"]');
this.encryptionProviderTypeError = page.locator('[data-test="add-cipher-key-auth-method-input-error"]');
this.encryptionProviderTypeTrigger = page.locator('[data-test="add-cipher-key-auth-method-input-trigger"]');
this.encryptionAlgorithmSelect = page.locator('[data-test="add-cipher-algorithm-input"]');
this.encryptionAlgorithmError = page.locator('[data-test="add-cipher-algorithm-input-error"]');
this.encryptionAlgorithmTrigger = page.locator('[data-test="add-cipher-algorithm-input-trigger"]');
// Toast
this.toastMessages = page.locator('[data-test="o-toast-message"]');
}
// ── Per-value option factory (runtime-dynamic value) ─────────────────────────
/**
* Returns the listbox option for a select identified by its parent data-test
* and the option's `data-test-value` (the OSelect value-specific convention).
*/
getSelectOptionByValue(parentDataTest, value) {
return this.page.locator(`[data-test="${parentDataTest}-option"][data-test-value="${value}"]`);
}
// ── Navigation ──────────────────────────────────────────────────────────────
/**
* Enterprise-availability probe. Navigates into the Cipher Keys tab and
* POSITIVELY detects whether the (enterprise-only) feature mounted, returning
* a boolean instead of throwing. Returns false on OSS where the route never
* mounts so the suite can SKIP rather than fail. Returns true on enterprise.
*/
async navigateToCipherKeysTab() {
await this.settingsMenu.waitFor({ state: 'visible' });
await this.settingsMenu.click();
await this.page.waitForLoadState('domcontentloaded');
try {
await this.cipherKeyTab.waitFor({ state: 'visible', timeout: 15000 });
await this.cipherKeyTab.click();
return true;
} catch {
return false;
}
}
async openAddCipherKeyForm() {
await this.addCipherKeyButton.waitFor({ state: 'visible' });
await this.addCipherKeyButton.click();
await this.continueButton.waitFor({ state: 'visible' });
}
// ── Name field ──────────────────────────────────────────────────────────────
async fillName(name) {
await this.nameInput.waitFor({ state: 'attached' });
await this.nameInput.fill(name, { force: true });
}
async clearName() {
await this.nameInput.waitFor({ state: 'attached' });
await this.nameInput.fill('', { force: true });
// Trigger blur so the error fires
await this.nameInput.dispatchEvent('blur');
}
async getNameError() {
return this.nameError;
}
// ── Store type ──────────────────────────────────────────────────────────────
async selectAkeylessStoreType() {
await this.storeTypeSelect.waitFor({ state: 'visible' });
await this.storeTypeSelect.click();
await this.storeTypeAkeylessOption.waitFor({ state: 'visible' });
await this.storeTypeAkeylessOption.click();
}
// ── Continue / Save ─────────────────────────────────────────────────────────
async clickContinue() {
await this.continueButton.waitFor({ state: 'visible' });
await this.continueButton.click();
}
async clickSave() {
await this.saveButton.waitFor({ state: 'visible' });
await this.saveButton.click();
}
// ── OpenObserve secret ──────────────────────────────────────────────────────
async fillSecret(secret) {
await this.secretInput.waitFor({ state: 'attached' });
await this.secretInput.fill(secret, { force: true });
}
// ── Akeyless fields ─────────────────────────────────────────────────────────
async fillAkeylessBaseUrl(url) {
await this.akeylessBaseUrlInput.waitFor({ state: 'attached' });
await this.akeylessBaseUrlInput.fill(url, { force: true });
}
async fillAkeylessAccessId(id) {
await this.akeylessAccessIdInput.waitFor({ state: 'attached' });
await this.akeylessAccessIdInput.fill(id, { force: true });
}
async fillAkeylessAccessKey(key) {
await this.akeylessAccessKeyInput.waitFor({ state: 'attached' });
await this.akeylessAccessKeyInput.fill(key, { force: true });
}
async fillStaticSecretName(name) {
await this.staticSecretNameInput.waitFor({ state: 'attached' });
await this.staticSecretNameInput.fill(name, { force: true });
}
async selectAuthMethod(value) {
await this.authMethodSelect.waitFor({ state: 'visible' });
await this.authMethodSelect.click();
const option = this.getSelectOptionByValue('add-cipher-key-auth-method-input', value);
await option.waitFor({ state: 'visible' });
await option.click();
}
async selectSecretType(value) {
await this.secretTypeSelect.waitFor({ state: 'visible' });
await this.secretTypeSelect.click();
const option = this.getSelectOptionByValue('add-cipher-key-secret-type-input', value);
await option.waitFor({ state: 'visible' });
await option.click();
}
// ── Toast assertion ─────────────────────────────────────────────────────────
async verifyToastMessage(expectedText) {
// Deterministic wait: poll the visible toast text contents until the expected
// message appears (or the assertion times out).
await expect
.poll(
async () => {
const texts = await this.toastMessages.allTextContents().catch(() => []);
return texts.map((t) => (t || '').toLowerCase());
},
{ timeout: 12000, intervals: [300] },
)
.toEqual(expect.arrayContaining([expect.stringContaining(expectedText.toLowerCase())]));
}
// ── Assertion helpers ────────────────────────────────────────────────────────
async assertNameErrorVisible() {
await expect(this.nameError).toBeVisible({ timeout: 5000 });
}
async assertStoreTypeErrorVisible() {
await expect(this.storeTypeError).toBeVisible({ timeout: 5000 });
}
async assertSecretErrorVisible() {
await expect(this.secretError).toBeVisible({ timeout: 5000 });
}
async assertAkeylessBaseUrlErrorVisible() {
await expect(this.akeylessBaseUrlError).toBeVisible({ timeout: 5000 });
}
async assertAkeylessBaseUrlErrorContains(text) {
await expect(this.akeylessBaseUrlError).toContainText(text, { timeout: 5000 });
}
async assertAkeylessAccessIdErrorVisible() {
await expect(this.akeylessAccessIdError).toBeVisible({ timeout: 5000 });
}
async assertAkeylessAccessKeyErrorVisible() {
await expect(this.akeylessAccessKeyError).toBeVisible({ timeout: 5000 });
}
async assertStaticSecretNameErrorVisible() {
await expect(this.staticSecretNameError).toBeVisible({ timeout: 5000 });
}
// ── AddEncryptionMechanism helpers ──────────────────────────────────────────
async selectEncryptionProviderType(value) {
// OSelect (reka-ui popover): clicking the root wrapper does NOT open the
// dropdown, and the trigger can open-then-close on a single click. Drive it
// through the shared helper (clicks the inner -trigger until aria-expanded).
await this.encryptionProviderTypeSelect.waitFor({ state: 'visible', timeout: 10000 });
await openOSelectDropdown(this.page, this.encryptionProviderTypeSelect);
const option = this.getSelectOptionByValue('add-cipher-key-auth-method-input', value);
await option.waitFor({ state: 'visible' });
await option.click();
}
async assertEncryptionProviderTypeErrorVisible() {
await expect(this.encryptionProviderTypeError).toBeVisible({ timeout: 5000 });
}
async assertEncryptionProviderTypeErrorHidden() {
await expect(this.encryptionProviderTypeError).toHaveCount(0);
}
async assertEncryptionProviderTypeSelected(label) {
await expect(this.encryptionProviderTypeTrigger).toHaveAttribute('data-test-selected-label', label, { timeout: 5000 });
}
async assertEncryptionAlgorithmErrorVisible() {
await expect(this.encryptionAlgorithmError).toBeVisible({ timeout: 5000 });
}
async assertEncryptionAlgorithmErrorHidden() {
await expect(this.encryptionAlgorithmError).toHaveCount(0);
}
async assertEncryptionAlgorithmSelected(label) {
await expect(this.encryptionAlgorithmTrigger).toHaveAttribute('data-test-selected-label', label, { timeout: 5000 });
}
// R3: Save is never disabled to gate validity — an invalid submit is blocked
// by the schema and surfaces field errors instead of a dead button.
async assertSaveButtonEnabled() {
await expect(this.saveButton).toBeEnabled({ timeout: 5000 });
}
async clickSave() {
await this.saveButton.click();
}
}