From c5f7c9e50d7ae1950d4ddb5e37fdcdbd4401a5e4 Mon Sep 17 00:00:00 2001 From: Robb Hamilton Date: Wed, 29 Jul 2026 14:06:29 -0400 Subject: [PATCH 1/4] CONSOLE-5196: Fix Playwright e2e flakes across multiple test suites Address several sources of flaky Playwright e2e test failures: 1. Alertmanager parallel worker interference: Merge receivers.spec.ts into alertmanager.spec.ts so all tests that mutate the shared alertmanager-main secret run on a single worker. 2. warmupSPA resilience: Wrap the goto + heading check in toPass() with retry intervals so a single slow cluster response doesn't cause immediate failure. 3. a11y test stabilization: Wait for loading indicators to clear before running axe-core analysis. Upgrade toBeAttached() checks to toBeVisible() in assertLoaded callbacks so pages are fully rendered before a11y checks run. 4. Topology test timeouts: Add test.setTimeout(300_000) to tests that create workloads via git URL, since validation + deployment regularly exceeds the 120s default. 5. Topology application name field: Handle the conditional rendering of the application name input by selecting "Create application" from the dropdown when existing applications are present. 6. Perspective-switcher cleanup: Add afterAll to remove the Console operator perspectives patch, preventing cluster-wide side effects from leaking into other tests. Co-Authored-By: Claude Opus 4.6 --- frontend/e2e/pages/base-page.ts | 6 +- frontend/e2e/pages/topology-page.ts | 6 +- .../alertmanager/alertmanager.spec.ts | 559 ++++++++++++++++- .../alertmanager/receivers/receivers.spec.ts | 563 ------------------ .../tests/console/crud/other-routes.spec.ts | 25 +- .../e2e/tests/topology/topology-ci.spec.ts | 4 + frontend/e2e/utils/a11y.ts | 23 + 7 files changed, 615 insertions(+), 571 deletions(-) delete mode 100644 frontend/e2e/tests/console/cluster-settings/alertmanager/receivers/receivers.spec.ts diff --git a/frontend/e2e/pages/base-page.ts b/frontend/e2e/pages/base-page.ts index 21c86aa2098..5fc54d3b16e 100644 --- a/frontend/e2e/pages/base-page.ts +++ b/frontend/e2e/pages/base-page.ts @@ -23,8 +23,10 @@ export async function setEditorContent(page: Page, content: string): Promise { - await page.goto('/', { waitUntil: 'domcontentloaded', timeout: 60_000 }); - await expect(page.getByTestId('page-heading')).toBeVisible({ timeout: 30_000 }); + await expect(async () => { + await page.goto('/', { waitUntil: 'domcontentloaded', timeout: 60_000 }); + await expect(page.getByTestId('page-heading')).toBeVisible({ timeout: 30_000 }); + }).toPass({ intervals: [1_000, 2_000, 5_000], timeout: 90_000 }); } export default abstract class BasePage { diff --git a/frontend/e2e/pages/topology-page.ts b/frontend/e2e/pages/topology-page.ts index 60ab11a91ad..5cbd5e86a9c 100644 --- a/frontend/e2e/pages/topology-page.ts +++ b/frontend/e2e/pages/topology-page.ts @@ -194,7 +194,11 @@ export class TopologyPage extends BasePage { } async fillApplicationName(appName: string): Promise { - await expect(this.applicationNameField).toBeVisible(); + if (await this.applicationDropdown.isVisible({ timeout: 2_000 }).catch(() => false)) { + await this.applicationDropdown.click(); + await this.page.getByRole('option', { name: 'Create application' }).click(); + } + await expect(this.applicationNameField).toBeVisible({ timeout: 10_000 }); await this.applicationNameField.fill(appName); await expect(this.applicationNameField).toHaveValue(appName); } diff --git a/frontend/e2e/tests/console/cluster-settings/alertmanager/alertmanager.spec.ts b/frontend/e2e/tests/console/cluster-settings/alertmanager/alertmanager.spec.ts index 4977a28a7c5..439f6d05da4 100644 --- a/frontend/e2e/tests/console/cluster-settings/alertmanager/alertmanager.spec.ts +++ b/frontend/e2e/tests/console/cluster-settings/alertmanager/alertmanager.spec.ts @@ -1,6 +1,9 @@ import { test, expect } from '../../../../fixtures'; import jsYaml from 'js-yaml'; -import { AlertmanagerPage } from '../../../../pages/alertmanager-page'; +import { + AlertmanagerPage, + getGlobalsAndReceiverConfig, +} from '../../../../pages/alertmanager-page'; import KubernetesClient from '../../../../clients/kubernetes-client'; import { resetAlertmanagerConfig } from './alertmanager-test-utils'; @@ -256,3 +259,557 @@ route: }); }); }); + +test.describe('Alertmanager Receiver Forms', { tag: ['@admin'] }, () => { + let alertmanager: AlertmanagerPage; + let k8sClient: KubernetesClient; + + test.beforeEach(async ({ page, k8sClient: client }) => { + alertmanager = new AlertmanagerPage(page); + k8sClient = client; + await resetAlertmanagerConfig(k8sClient); + }); + + test.afterEach(async () => { + await resetAlertmanagerConfig(k8sClient); + }); + + test('creates and edits Webhook Receiver correctly', async ({ page }) => { + const receiverName = `WebhookReceiver-${Date.now()}`; + const configName = 'webhook_configs'; + const label = 'severity = warning'; + const webhookURL = 'http://mywebhookurl'; + const updatedWebhookURL = 'http://myupdatedwebhookurl'; + + await test.step('Create Webhook Receiver', async () => { + await alertmanager.navigateToAlertmanager(); + await alertmanager.createReceiver(receiverName, configName); + + await alertmanager.showAdvancedConfiguration(); + await expect(page.getByTestId('send-resolved-alerts')).toBeChecked(); + + await page.getByTestId('webhook-url').fill(webhookURL); + await page.getByTestId('label-0').fill(label); + + await alertmanager.save(); + }); + + await test.step('Verify Webhook Receiver was created correctly', async () => { + await alertmanager.validateReceiverInList(receiverName); + + await expect(async () => { + await alertmanager.navigateToYAMLPage(); + const yamlContent = await alertmanager.getYAMLContent(); + const configs = getGlobalsAndReceiverConfig(receiverName, configName, yamlContent); + + expect(configs.receiverConfig.url).toBe(webhookURL); + expect(configs.receiverConfig).not.toHaveProperty('send_resolved'); + }).toPass({ intervals: [2_000, 3_000, 5_000], timeout: 30_000 }); + }); + + await test.step('Edit Webhook Receiver and save advanced fields', async () => { + await alertmanager.navigateToEditReceiver(receiverName); + + await expect(page.getByTestId('webhook-url')).toHaveValue(webhookURL); + + await page.getByTestId('webhook-url').clear(); + await page.getByTestId('webhook-url').fill(updatedWebhookURL); + + await alertmanager.showAdvancedConfiguration(); + await page.getByTestId('send-resolved-alerts').uncheck(); + + await alertmanager.save(); + }); + + await test.step('Verify advanced fields were saved correctly', async () => { + await alertmanager.navigateToEditReceiver(receiverName); + await alertmanager.showAdvancedConfiguration(); + + await expect(page.getByTestId('send-resolved-alerts')).not.toBeChecked(); + }); + + await test.step('Verify YAML has correct config', async () => { + await expect(async () => { + await alertmanager.navigateToYAMLPage(); + const yamlContent = await alertmanager.getYAMLContent(); + const configs = getGlobalsAndReceiverConfig(receiverName, configName, yamlContent); + + expect(configs.receiverConfig.url).toBe(updatedWebhookURL); + expect(configs.receiverConfig.send_resolved).toBe(false); + }).toPass({ intervals: [2_000, 3_000, 5_000], timeout: 30_000 }); + }); + }); + + test('creates and edits Email Receiver correctly', async ({ page }) => { + const receiverName = `EmailReceiver-${Date.now()}`; + const configName = 'email_configs'; + const localhost = 'localhost'; + const label = 'severity = warning'; + const emailTo = 'you@there.com'; + const emailFrom = 'me@here.com'; + const emailSmarthost = 'smarthost:8080'; + const username = 'username'; + const password = 'password'; + const identity = 'identity'; + const secret = 'secret'; + const html = 'myhtml'; + + await test.step('Create Email Receiver with basic configuration', async () => { + await alertmanager.navigateToAlertmanager(); + await alertmanager.createReceiver(receiverName, configName); + + // Verify defaults before smtp change + const saveAsDefaultCheckbox = page.getByTestId('save-as-default'); + await expect(saveAsDefaultCheckbox).toBeDisabled(); + + const emailHelloInput = page.getByTestId('email-hello'); + await expect(emailHelloInput).toHaveValue(localhost); + + const requireTlsCheckbox = page.getByTestId('email-require-tls'); + await expect(requireTlsCheckbox).toBeChecked(); + + // Check advanced configuration defaults + await alertmanager.showAdvancedConfiguration(); + const sendResolvedCheckbox = page.getByTestId('send-resolved-alerts'); + await expect(sendResolvedCheckbox).not.toBeChecked(); + + const emailHtmlInput = page.getByTestId('email-html'); + await expect(emailHtmlInput).toHaveValue('{{ template "email.default.html" . }}'); + + // Fill in required fields + await page.getByTestId('email-to').fill(emailTo); + await page.getByTestId('email-from').fill(emailFrom); + + // Save as default should now be enabled + await expect(saveAsDefaultCheckbox).toBeEnabled(); + + await page.getByTestId('email-smarthost').fill(emailSmarthost); + await page.getByTestId('label-0').fill(label); + + await alertmanager.save(); + }); + + await test.step('Verify Email Receiver was created correctly', async () => { + await alertmanager.validateReceiverInList(receiverName); + + await expect(async () => { + await alertmanager.navigateToYAMLPage(); + const yamlContent = await alertmanager.getYAMLContent(); + const configs = getGlobalsAndReceiverConfig(receiverName, configName, yamlContent); + + // Verify values are NOT in globals + expect(configs.globals).not.toHaveProperty('email_to'); + expect(configs.globals).not.toHaveProperty('smtp_from'); + expect(configs.globals).not.toHaveProperty('smtp_smarthost'); + expect(configs.globals).not.toHaveProperty('smtp_require_tls'); + + // Verify values ARE in receiver config + expect(configs.receiverConfig.to).toBe(emailTo); + expect(configs.receiverConfig.from).toBe(emailFrom); + expect(configs.receiverConfig.smarthost).toBe(emailSmarthost); + // require_tls should not be in receiver config (unchanged from global) + expect(configs.receiverConfig).not.toHaveProperty('require_tls'); + }).toPass({ intervals: [2_000, 3_000, 5_000], timeout: 30_000 }); + }); + + await test.step('Edit receiver with auth and advanced fields', async () => { + await alertmanager.navigateToEditReceiver(receiverName); + + // Verify existing values + await expect(page.getByTestId('email-to')).toHaveValue(emailTo); + + const saveAsDefaultCheckbox = page.getByTestId('save-as-default'); + await expect(saveAsDefaultCheckbox).toBeEnabled(); + await expect(saveAsDefaultCheckbox).not.toBeChecked(); + + await expect(page.getByTestId('email-from')).toHaveValue(emailFrom); + await expect(page.getByTestId('email-hello')).toHaveValue(localhost); + + // Add auth fields + await page.getByTestId('email-auth-username').fill(username); + await page.getByTestId('email-auth-password').fill(password); + await page.getByTestId('email-auth-identity').fill(identity); + await page.getByTestId('email-auth-secret').fill(secret); + + // Uncheck require TLS + await page.getByTestId('email-require-tls').uncheck(); + + // Update advanced fields + await alertmanager.showAdvancedConfiguration(); + await page.getByTestId('send-resolved-alerts').check(); + + const htmlInput = page.getByTestId('email-html'); + await htmlInput.clear(); + await htmlInput.fill(html); + + await alertmanager.save(); + }); + + await test.step('Verify auth and advanced fields were saved correctly', async () => { + await expect(async () => { + await alertmanager.navigateToYAMLPage(); + const yamlContent = await alertmanager.getYAMLContent(); + const configs = getGlobalsAndReceiverConfig(receiverName, configName, yamlContent); + + // Auth username should NOT be in globals + expect(configs.globals).not.toHaveProperty('smtp_auth_username'); + + // Auth fields should be in receiver config + expect(configs.receiverConfig.auth_username).toBe(username); + expect(configs.receiverConfig.auth_password).toBe(password); + expect(configs.receiverConfig.auth_identity).toBe(identity); + expect(configs.receiverConfig.auth_secret).toBe(secret); + + // require_tls should now be explicitly false in receiver config + expect(configs.receiverConfig.require_tls).toBe(false); + + // Advanced fields + expect(configs.receiverConfig.send_resolved).toBe(true); + expect(configs.receiverConfig.html).toBe(html); + }).toPass({ intervals: [2_000, 3_000, 5_000], timeout: 30_000 }); + }); + + await test.step('Save fields as global defaults', async () => { + await alertmanager.navigateToEditReceiver(receiverName); + + const saveAsDefaultCheckbox = page.getByTestId('save-as-default'); + await expect(saveAsDefaultCheckbox).not.toBeChecked(); + await saveAsDefaultCheckbox.check(); + + await alertmanager.save(); + }); + + await test.step('Verify fields were saved as globals', async () => { + await expect(async () => { + await alertmanager.navigateToYAMLPage(); + const yamlContent = await alertmanager.getYAMLContent(); + const configs = getGlobalsAndReceiverConfig(receiverName, configName, yamlContent); + + // Verify values are now in globals + expect(configs.globals.smtp_from).toBe(emailFrom); + expect(configs.globals.smtp_hello).toBe(localhost); + expect(configs.globals.smtp_smarthost).toBe(emailSmarthost); + expect(configs.globals.smtp_auth_username).toBe(username); + expect(configs.globals.smtp_auth_password).toBe(password); + expect(configs.globals.smtp_auth_identity).toBe(identity); + expect(configs.globals.smtp_auth_secret).toBe(secret); + expect(configs.globals.smtp_require_tls).toBe(false); + + // Non-global field (to) should still be in receiver config + expect(configs.receiverConfig.to).toBe(emailTo); + }).toPass({ intervals: [2_000, 3_000, 5_000], timeout: 30_000 }); + }); + }); + + test('creates and edits Slack Receiver correctly', async ({ page }) => { + const receiverName = `SlackReceiver-${Date.now()}`; + const configName = 'slack_configs'; + const label = 'severity = warning'; + const slackAPIURL = 'http://myslackapi'; + const slackChannel = 'myslackchannel'; + const slackIconURL = 'http://slackiconurl'; + const slackUsername = 'slackusername'; + + await test.step('Create Slack Receiver with basic configuration', async () => { + await alertmanager.navigateToAlertmanager(); + await alertmanager.createReceiver(receiverName, configName); + + await expect(page.getByTestId('save-as-default')).toBeDisabled(); + + await alertmanager.showAdvancedConfiguration(); + + // Verify defaults + await expect(page.getByTestId('send-resolved-alerts')).not.toBeChecked(); + await expect(page.getByTestId('slack-icon-url')).toHaveValue( + '{{ template "slack.default.iconurl" .}}', + ); + await expect(page.getByTestId('slack-icon-emoji')).toBeHidden(); + + // Switch to Emoji radio and verify + await page.getByTestId('Emoji-radio-input').click(); + await expect(page.getByTestId('slack-icon-url')).toBeHidden(); + await expect(page.getByTestId('slack-icon-emoji')).toHaveValue( + '{{ template "slack.default.iconemoji" .}}', + ); + + // Switch back to URL for the test + await page.getByTestId('URL-radio-input').click(); + + await expect(page.getByTestId('slack-username')).toHaveValue( + '{{ template "slack.default.username" . }}', + ); + await expect(page.getByTestId('slack-link-names')).not.toBeChecked(); + + // Fill required fields + await page.getByTestId('slack-api-url').fill(slackAPIURL); + await expect(page.getByTestId('save-as-default')).toBeEnabled(); + + await page.getByTestId('slack-channel').fill(slackChannel); + await page.getByTestId('label-0').fill(label); + + await alertmanager.save(); + }); + + await test.step('Verify Slack Receiver was created correctly', async () => { + await alertmanager.validateReceiverInList(receiverName); + + await expect(async () => { + await alertmanager.navigateToYAMLPage(); + const yamlContent = await alertmanager.getYAMLContent(); + const configs = getGlobalsAndReceiverConfig(receiverName, configName, yamlContent); + + expect(configs.globals).not.toHaveProperty('slack_api_url'); + expect(configs.receiverConfig.channel).toBe(slackChannel); + expect(configs.receiverConfig.api_url).toBe(slackAPIURL); + // Advanced fields are not saved since they equal their global values + expect(configs.receiverConfig).not.toHaveProperty('send_resolved'); + expect(configs.receiverConfig).not.toHaveProperty('username'); + }).toPass({ intervals: [2_000, 3_000, 5_000], timeout: 30_000 }); + }); + + await test.step('Save globals and advanced fields', async () => { + await alertmanager.navigateToEditReceiver(receiverName); + + await expect(page.getByTestId('slack-channel')).toHaveValue(slackChannel); + await expect(page.getByTestId('save-as-default')).toBeEnabled(); + await expect(page.getByTestId('slack-api-url')).toHaveValue(slackAPIURL); + + await alertmanager.showAdvancedConfiguration(); + + await page.getByTestId('send-resolved-alerts').check(); + + await page.getByTestId('slack-icon-url').clear(); + await page.getByTestId('slack-icon-url').fill(slackIconURL); + + await page.getByTestId('slack-username').clear(); + await page.getByTestId('slack-username').fill(slackUsername); + + await page.getByTestId('slack-link-names').check(); + + await page.getByTestId('save-as-default').check(); + + await alertmanager.save(); + }); + + await test.step('Verify advanced fields were saved correctly', async () => { + await alertmanager.navigateToEditReceiver(receiverName); + await alertmanager.showAdvancedConfiguration(); + + await expect(page.getByTestId('send-resolved-alerts')).toBeChecked(); + await expect(page.getByTestId('slack-icon-url')).toHaveValue(slackIconURL); + await expect(page.getByTestId('slack-icon-emoji')).toBeHidden(); + await expect(page.getByTestId('slack-username')).toHaveValue(slackUsername); + await expect(page.getByTestId('slack-link-names')).toBeChecked(); + }); + + await test.step('Verify YAML has correct global and receiver config', async () => { + await expect(async () => { + await alertmanager.navigateToYAMLPage(); + const yamlContent = await alertmanager.getYAMLContent(); + const configs = getGlobalsAndReceiverConfig(receiverName, configName, yamlContent); + + expect(configs.globals.slack_api_url).toBe(slackAPIURL); + expect(configs.receiverConfig).not.toHaveProperty('api_url'); + expect(configs.receiverConfig.channel).toBe('myslackchannel'); + expect(configs.receiverConfig.send_resolved).toBe(true); + expect(configs.receiverConfig.icon_url).toBe(slackIconURL); + expect(configs.receiverConfig.username).toBe(slackUsername); + expect(configs.receiverConfig.link_names).toBe(true); + }).toPass({ intervals: [2_000, 3_000, 5_000], timeout: 30_000 }); + }); + }); + + test('creates and edits PagerDuty Receiver correctly', async ({ page }) => { + test.setTimeout(180_000); + + const receiverName = `PagerDutyReceiver-${Date.now()}`; + const configName = 'pagerduty_configs'; + const severity = 'severity'; + const label = `${severity} = warning`; + const pagerDutyClient = '{{ template "pagerduty.default.client" . }}'; + const pagerDutyClientURL = '{{ template "pagerduty.default.clientURL" . }}'; + const pagerDutyURL1 = 'http://pagerduty-url-specific-to-receiver'; + const pagerDutyURL2 = 'http://global-pagerduty-url'; + const pagerDutyURL3 = 'http://pagerduty-url-specific-to-receiver'; + const clientURL = 'http://updated-client-url'; + const pagerDutyDescription = 'new description'; + + await test.step('Create PagerDuty Receiver with basic configuration', async () => { + await alertmanager.navigateToAlertmanager(); + await alertmanager.createReceiver(receiverName, configName); + + await page.getByTestId('integration-key').fill(''); + + // Verify default URL + await expect(page.getByTestId('pagerduty-url')).toHaveValue( + 'https://events.pagerduty.com/v2/enqueue', + ); + + // Check advanced configuration defaults + await alertmanager.showAdvancedConfiguration(); + await expect(page.getByTestId('send-resolved-alerts')).toBeChecked(); + await expect(page.getByTestId('pagerduty-client')).toHaveValue(pagerDutyClient); + await expect(page.getByTestId('pagerduty-client-url')).toHaveValue(pagerDutyClientURL); + await expect(page.getByTestId('pagerduty-description')).toHaveValue( + '{{ template "pagerduty.default.description" .}}', + ); + await expect(page.getByTestId('pagerduty-severity')).toHaveValue('error'); + + await page.getByTestId('label-0').fill(label); + await alertmanager.save(); + }); + + await test.step('Verify PagerDuty Receiver was created correctly', async () => { + await alertmanager.validateReceiverInList(receiverName); + }); + + await test.step('Update pagerduty_url', async () => { + await alertmanager.navigateToEditReceiver(receiverName); + + // Save as default checkbox disabled when url equals global url + await expect(page.getByTestId('save-as-default')).toBeDisabled(); + + // Changing url enables Save as default checkbox + const urlInput = page.getByTestId('pagerduty-url'); + await urlInput.clear(); + await urlInput.fill(pagerDutyURL1); + + await expect(page.getByTestId('save-as-default')).toBeEnabled(); + await alertmanager.save(); + }); + + await test.step('Verify pagerduty_url was saved with Receiver and not global', async () => { + await expect(async () => { + await alertmanager.navigateToYAMLPage(); + const yamlContent = await alertmanager.getYAMLContent(); + const configs = getGlobalsAndReceiverConfig(receiverName, configName, yamlContent); + + expect(configs.globals).not.toHaveProperty('pagerduty_url'); + expect(configs.receiverConfig.url).toBe(pagerDutyURL1); + }).toPass({ intervals: [2_000, 3_000, 5_000], timeout: 30_000 }); + }); + + await test.step('Save pagerduty_url as global', async () => { + await alertmanager.navigateToEditReceiver(receiverName); + + const urlInput = page.getByTestId('pagerduty-url'); + await urlInput.clear(); + await urlInput.fill(pagerDutyURL2); + + const saveAsDefaultCheckbox = page.getByTestId('save-as-default'); + await expect(saveAsDefaultCheckbox).toBeEnabled(); + await saveAsDefaultCheckbox.check(); + + await alertmanager.save(); + }); + + await test.step('Verify pagerduty_url was saved as global', async () => { + await expect(async () => { + await alertmanager.navigateToYAMLPage(); + const yamlContent = await alertmanager.getYAMLContent(); + const configs = getGlobalsAndReceiverConfig(receiverName, configName, yamlContent); + + expect(configs.globals.pagerduty_url).toBe(pagerDutyURL2); + expect(configs.receiverConfig).not.toHaveProperty('url'); + }).toPass({ intervals: [2_000, 3_000, 5_000], timeout: 30_000 }); + }); + + await test.step('Add pagerduty_url to receiver with existing global', async () => { + await alertmanager.navigateToEditReceiver(receiverName); + + const urlInput = page.getByTestId('pagerduty-url'); + await urlInput.clear(); + await urlInput.fill(pagerDutyURL3); + + const saveAsDefaultCheckbox = page.getByTestId('save-as-default'); + await expect(saveAsDefaultCheckbox).toBeEnabled(); + await expect(saveAsDefaultCheckbox).not.toBeChecked(); + + await alertmanager.save(); + }); + + await test.step( + 'Verify pagerduty_url saved with Receiver and global still exists', + async () => { + await expect(async () => { + await alertmanager.navigateToYAMLPage(); + const yamlContent = await alertmanager.getYAMLContent(); + const configs = getGlobalsAndReceiverConfig(receiverName, configName, yamlContent); + + expect(configs.globals.pagerduty_url).toBe(pagerDutyURL2); + expect(configs.receiverConfig.url).toBe(pagerDutyURL3); + }).toPass({ intervals: [2_000, 3_000, 5_000], timeout: 30_000 }); + }, + ); + + await test.step('Update advanced configuration fields', async () => { + await alertmanager.navigateToEditReceiver(receiverName); + await alertmanager.showAdvancedConfiguration(); + + const sendResolvedCheckbox = page.getByTestId('send-resolved-alerts'); + await expect(sendResolvedCheckbox).toBeChecked(); + await sendResolvedCheckbox.uncheck(); + await expect(sendResolvedCheckbox).not.toBeChecked(); + + await page.getByTestId('pagerduty-client').clear(); + await page.getByTestId('pagerduty-client').fill('updated-client'); + + await page.getByTestId('pagerduty-client-url').clear(); + await page.getByTestId('pagerduty-client-url').fill(clientURL); + + await alertmanager.save(); + }); + + await test.step('Verify changed fields are saved with Receiver', async () => { + await expect(async () => { + await alertmanager.navigateToYAMLPage(); + const yamlContent = await alertmanager.getYAMLContent(); + const configs = getGlobalsAndReceiverConfig(receiverName, configName, yamlContent); + + expect(configs.receiverConfig.send_resolved).toBe(false); + expect(configs.receiverConfig.client).toBe('updated-client'); + expect(configs.receiverConfig.client_url).toBe('http://updated-client-url'); + expect(configs.receiverConfig.description).toBeUndefined(); + expect(configs.receiverConfig.severity).toBeUndefined(); + }).toPass({ intervals: [2_000, 3_000, 5_000], timeout: 30_000 }); + }); + + await test.step('Restore defaults, change desc and severity', async () => { + await alertmanager.navigateToEditReceiver(receiverName); + await alertmanager.showAdvancedConfiguration(); + + const sendResolvedCheckbox = page.getByTestId('send-resolved-alerts'); + await expect(sendResolvedCheckbox).not.toBeChecked(); + await sendResolvedCheckbox.check(); + await expect(sendResolvedCheckbox).toBeChecked(); + + await page.getByTestId('pagerduty-client').clear(); + await page.getByTestId('pagerduty-client').fill(pagerDutyClient); + + await page.getByTestId('pagerduty-client-url').clear(); + await page.getByTestId('pagerduty-client-url').fill(pagerDutyClientURL); + + await page.getByTestId('pagerduty-description').clear(); + await page.getByTestId('pagerduty-description').fill(pagerDutyDescription); + + await page.getByTestId('pagerduty-severity').clear(); + await page.getByTestId('pagerduty-severity').fill(severity); + + await alertmanager.save(); + }); + + await test.step('Verify defaults removed from config, desc and severity saved', async () => { + await expect(async () => { + await alertmanager.navigateToYAMLPage(); + const yamlContent = await alertmanager.getYAMLContent(); + const configs = getGlobalsAndReceiverConfig(receiverName, configName, yamlContent); + + expect(configs.receiverConfig.send_resolved).toBeUndefined(); + expect(configs.receiverConfig.client).toBeUndefined(); + expect(configs.receiverConfig.client_url).toBeUndefined(); + expect(configs.receiverConfig.description).toBe(pagerDutyDescription); + expect(configs.receiverConfig.severity).toBe(severity); + }).toPass({ intervals: [2_000, 3_000, 5_000], timeout: 30_000 }); + }); + }); +}); diff --git a/frontend/e2e/tests/console/cluster-settings/alertmanager/receivers/receivers.spec.ts b/frontend/e2e/tests/console/cluster-settings/alertmanager/receivers/receivers.spec.ts deleted file mode 100644 index 9ef5afd2ec9..00000000000 --- a/frontend/e2e/tests/console/cluster-settings/alertmanager/receivers/receivers.spec.ts +++ /dev/null @@ -1,563 +0,0 @@ -import { test, expect } from '../../../../../fixtures'; -import { - AlertmanagerPage, - getGlobalsAndReceiverConfig, -} from '../../../../../pages/alertmanager-page'; -import KubernetesClient from '../../../../../clients/kubernetes-client'; -import { resetAlertmanagerConfig } from '../alertmanager-test-utils'; - -test.describe('Alertmanager Receiver Forms', { tag: ['@admin'] }, () => { - test.describe.configure({ mode: 'serial' }); - - let alertmanager: AlertmanagerPage; - let k8sClient: KubernetesClient; - - test.beforeEach(async ({ page, k8sClient: client }) => { - alertmanager = new AlertmanagerPage(page); - k8sClient = client; - await resetAlertmanagerConfig(k8sClient); - }); - - test.afterEach(async () => { - await resetAlertmanagerConfig(k8sClient); - }); - - test('creates and edits Webhook Receiver correctly', async ({ page }) => { - const receiverName = `WebhookReceiver-${Date.now()}`; - const configName = 'webhook_configs'; - const label = 'severity = warning'; - const webhookURL = 'http://mywebhookurl'; - const updatedWebhookURL = 'http://myupdatedwebhookurl'; - - await test.step('Create Webhook Receiver', async () => { - await alertmanager.navigateToAlertmanager(); - await alertmanager.createReceiver(receiverName, configName); - - await alertmanager.showAdvancedConfiguration(); - await expect(page.getByTestId('send-resolved-alerts')).toBeChecked(); - - await page.getByTestId('webhook-url').fill(webhookURL); - await page.getByTestId('label-0').fill(label); - - await alertmanager.save(); - }); - - await test.step('Verify Webhook Receiver was created correctly', async () => { - await alertmanager.validateReceiverInList(receiverName); - - await expect(async () => { - await alertmanager.navigateToYAMLPage(); - const yamlContent = await alertmanager.getYAMLContent(); - const configs = getGlobalsAndReceiverConfig(receiverName, configName, yamlContent); - - expect(configs.receiverConfig.url).toBe(webhookURL); - expect(configs.receiverConfig).not.toHaveProperty('send_resolved'); - }).toPass({ intervals: [2_000, 3_000, 5_000], timeout: 30_000 }); - }); - - await test.step('Edit Webhook Receiver and save advanced fields', async () => { - await alertmanager.navigateToEditReceiver(receiverName); - - await expect(page.getByTestId('webhook-url')).toHaveValue(webhookURL); - - await page.getByTestId('webhook-url').clear(); - await page.getByTestId('webhook-url').fill(updatedWebhookURL); - - await alertmanager.showAdvancedConfiguration(); - await page.getByTestId('send-resolved-alerts').uncheck(); - - await alertmanager.save(); - }); - - await test.step('Verify advanced fields were saved correctly', async () => { - await alertmanager.navigateToEditReceiver(receiverName); - await alertmanager.showAdvancedConfiguration(); - - await expect(page.getByTestId('send-resolved-alerts')).not.toBeChecked(); - }); - - await test.step('Verify YAML has correct config', async () => { - await expect(async () => { - await alertmanager.navigateToYAMLPage(); - const yamlContent = await alertmanager.getYAMLContent(); - const configs = getGlobalsAndReceiverConfig(receiverName, configName, yamlContent); - - expect(configs.receiverConfig.url).toBe(updatedWebhookURL); - expect(configs.receiverConfig.send_resolved).toBe(false); - }).toPass({ intervals: [2_000, 3_000, 5_000], timeout: 30_000 }); - }); - }); - - test('creates and edits Email Receiver correctly', async ({ page }) => { - const receiverName = `EmailReceiver-${Date.now()}`; - const configName = 'email_configs'; - const localhost = 'localhost'; - const label = 'severity = warning'; - const emailTo = 'you@there.com'; - const emailFrom = 'me@here.com'; - const emailSmarthost = 'smarthost:8080'; - const username = 'username'; - const password = 'password'; - const identity = 'identity'; - const secret = 'secret'; - const html = 'myhtml'; - - await test.step('Create Email Receiver with basic configuration', async () => { - await alertmanager.navigateToAlertmanager(); - await alertmanager.createReceiver(receiverName, configName); - - // Verify defaults before smtp change - const saveAsDefaultCheckbox = page.getByTestId('save-as-default'); - await expect(saveAsDefaultCheckbox).toBeDisabled(); - - const emailHelloInput = page.getByTestId('email-hello'); - await expect(emailHelloInput).toHaveValue(localhost); - - const requireTlsCheckbox = page.getByTestId('email-require-tls'); - await expect(requireTlsCheckbox).toBeChecked(); - - // Check advanced configuration defaults - await alertmanager.showAdvancedConfiguration(); - const sendResolvedCheckbox = page.getByTestId('send-resolved-alerts'); - await expect(sendResolvedCheckbox).not.toBeChecked(); - - const emailHtmlInput = page.getByTestId('email-html'); - await expect(emailHtmlInput).toHaveValue('{{ template "email.default.html" . }}'); - - // Fill in required fields - await page.getByTestId('email-to').fill(emailTo); - await page.getByTestId('email-from').fill(emailFrom); - - // Save as default should now be enabled - await expect(saveAsDefaultCheckbox).toBeEnabled(); - - await page.getByTestId('email-smarthost').fill(emailSmarthost); - await page.getByTestId('label-0').fill(label); - - await alertmanager.save(); - }); - - await test.step('Verify Email Receiver was created correctly', async () => { - await alertmanager.validateReceiverInList(receiverName); - - await expect(async () => { - await alertmanager.navigateToYAMLPage(); - const yamlContent = await alertmanager.getYAMLContent(); - const configs = getGlobalsAndReceiverConfig(receiverName, configName, yamlContent); - - // Verify values are NOT in globals - expect(configs.globals).not.toHaveProperty('email_to'); - expect(configs.globals).not.toHaveProperty('smtp_from'); - expect(configs.globals).not.toHaveProperty('smtp_smarthost'); - expect(configs.globals).not.toHaveProperty('smtp_require_tls'); - - // Verify values ARE in receiver config - expect(configs.receiverConfig.to).toBe(emailTo); - expect(configs.receiverConfig.from).toBe(emailFrom); - expect(configs.receiverConfig.smarthost).toBe(emailSmarthost); - // require_tls should not be in receiver config (unchanged from global) - expect(configs.receiverConfig).not.toHaveProperty('require_tls'); - }).toPass({ intervals: [2_000, 3_000, 5_000], timeout: 30_000 }); - }); - - await test.step('Edit receiver with auth and advanced fields', async () => { - await alertmanager.navigateToEditReceiver(receiverName); - - // Verify existing values - await expect(page.getByTestId('email-to')).toHaveValue(emailTo); - - const saveAsDefaultCheckbox = page.getByTestId('save-as-default'); - await expect(saveAsDefaultCheckbox).toBeEnabled(); - await expect(saveAsDefaultCheckbox).not.toBeChecked(); - - await expect(page.getByTestId('email-from')).toHaveValue(emailFrom); - await expect(page.getByTestId('email-hello')).toHaveValue(localhost); - - // Add auth fields - await page.getByTestId('email-auth-username').fill(username); - await page.getByTestId('email-auth-password').fill(password); - await page.getByTestId('email-auth-identity').fill(identity); - await page.getByTestId('email-auth-secret').fill(secret); - - // Uncheck require TLS - await page.getByTestId('email-require-tls').uncheck(); - - // Update advanced fields - await alertmanager.showAdvancedConfiguration(); - await page.getByTestId('send-resolved-alerts').check(); - - const htmlInput = page.getByTestId('email-html'); - await htmlInput.clear(); - await htmlInput.fill(html); - - await alertmanager.save(); - }); - - await test.step('Verify auth and advanced fields were saved correctly', async () => { - await expect(async () => { - await alertmanager.navigateToYAMLPage(); - const yamlContent = await alertmanager.getYAMLContent(); - const configs = getGlobalsAndReceiverConfig(receiverName, configName, yamlContent); - - // Auth username should NOT be in globals - expect(configs.globals).not.toHaveProperty('smtp_auth_username'); - - // Auth fields should be in receiver config - expect(configs.receiverConfig.auth_username).toBe(username); - expect(configs.receiverConfig.auth_password).toBe(password); - expect(configs.receiverConfig.auth_identity).toBe(identity); - expect(configs.receiverConfig.auth_secret).toBe(secret); - - // require_tls should now be explicitly false in receiver config - expect(configs.receiverConfig.require_tls).toBe(false); - - // Advanced fields - expect(configs.receiverConfig.send_resolved).toBe(true); - expect(configs.receiverConfig.html).toBe(html); - }).toPass({ intervals: [2_000, 3_000, 5_000], timeout: 30_000 }); - }); - - await test.step('Save fields as global defaults', async () => { - await alertmanager.navigateToEditReceiver(receiverName); - - const saveAsDefaultCheckbox = page.getByTestId('save-as-default'); - await expect(saveAsDefaultCheckbox).not.toBeChecked(); - await saveAsDefaultCheckbox.check(); - - await alertmanager.save(); - }); - - await test.step('Verify fields were saved as globals', async () => { - await expect(async () => { - await alertmanager.navigateToYAMLPage(); - const yamlContent = await alertmanager.getYAMLContent(); - const configs = getGlobalsAndReceiverConfig(receiverName, configName, yamlContent); - - // Verify values are now in globals - expect(configs.globals.smtp_from).toBe(emailFrom); - expect(configs.globals.smtp_hello).toBe(localhost); - expect(configs.globals.smtp_smarthost).toBe(emailSmarthost); - expect(configs.globals.smtp_auth_username).toBe(username); - expect(configs.globals.smtp_auth_password).toBe(password); - expect(configs.globals.smtp_auth_identity).toBe(identity); - expect(configs.globals.smtp_auth_secret).toBe(secret); - expect(configs.globals.smtp_require_tls).toBe(false); - - // Non-global field (to) should still be in receiver config - expect(configs.receiverConfig.to).toBe(emailTo); - }).toPass({ intervals: [2_000, 3_000, 5_000], timeout: 30_000 }); - }); - }); - - test('creates and edits Slack Receiver correctly', async ({ page }) => { - const receiverName = `SlackReceiver-${Date.now()}`; - const configName = 'slack_configs'; - const label = 'severity = warning'; - const slackAPIURL = 'http://myslackapi'; - const slackChannel = 'myslackchannel'; - const slackIconURL = 'http://slackiconurl'; - const slackUsername = 'slackusername'; - - await test.step('Create Slack Receiver with basic configuration', async () => { - await alertmanager.navigateToAlertmanager(); - await alertmanager.createReceiver(receiverName, configName); - - await expect(page.getByTestId('save-as-default')).toBeDisabled(); - - await alertmanager.showAdvancedConfiguration(); - - // Verify defaults - await expect(page.getByTestId('send-resolved-alerts')).not.toBeChecked(); - await expect(page.getByTestId('slack-icon-url')).toHaveValue( - '{{ template "slack.default.iconurl" .}}', - ); - await expect(page.getByTestId('slack-icon-emoji')).toBeHidden(); - - // Switch to Emoji radio and verify - await page.getByTestId('Emoji-radio-input').click(); - await expect(page.getByTestId('slack-icon-url')).toBeHidden(); - await expect(page.getByTestId('slack-icon-emoji')).toHaveValue( - '{{ template "slack.default.iconemoji" .}}', - ); - - // Switch back to URL for the test - await page.getByTestId('URL-radio-input').click(); - - await expect(page.getByTestId('slack-username')).toHaveValue( - '{{ template "slack.default.username" . }}', - ); - await expect(page.getByTestId('slack-link-names')).not.toBeChecked(); - - // Fill required fields - await page.getByTestId('slack-api-url').fill(slackAPIURL); - await expect(page.getByTestId('save-as-default')).toBeEnabled(); - - await page.getByTestId('slack-channel').fill(slackChannel); - await page.getByTestId('label-0').fill(label); - - await alertmanager.save(); - }); - - await test.step('Verify Slack Receiver was created correctly', async () => { - await alertmanager.validateReceiverInList(receiverName); - - await expect(async () => { - await alertmanager.navigateToYAMLPage(); - const yamlContent = await alertmanager.getYAMLContent(); - const configs = getGlobalsAndReceiverConfig(receiverName, configName, yamlContent); - - expect(configs.globals).not.toHaveProperty('slack_api_url'); - expect(configs.receiverConfig.channel).toBe(slackChannel); - expect(configs.receiverConfig.api_url).toBe(slackAPIURL); - // Advanced fields are not saved since they equal their global values - expect(configs.receiverConfig).not.toHaveProperty('send_resolved'); - expect(configs.receiverConfig).not.toHaveProperty('username'); - }).toPass({ intervals: [2_000, 3_000, 5_000], timeout: 30_000 }); - }); - - await test.step('Save globals and advanced fields', async () => { - await alertmanager.navigateToEditReceiver(receiverName); - - await expect(page.getByTestId('slack-channel')).toHaveValue(slackChannel); - await expect(page.getByTestId('save-as-default')).toBeEnabled(); - await expect(page.getByTestId('slack-api-url')).toHaveValue(slackAPIURL); - - await alertmanager.showAdvancedConfiguration(); - - await page.getByTestId('send-resolved-alerts').check(); - - await page.getByTestId('slack-icon-url').clear(); - await page.getByTestId('slack-icon-url').fill(slackIconURL); - - await page.getByTestId('slack-username').clear(); - await page.getByTestId('slack-username').fill(slackUsername); - - await page.getByTestId('slack-link-names').check(); - - await page.getByTestId('save-as-default').check(); - - await alertmanager.save(); - }); - - await test.step('Verify advanced fields were saved correctly', async () => { - await alertmanager.navigateToEditReceiver(receiverName); - await alertmanager.showAdvancedConfiguration(); - - await expect(page.getByTestId('send-resolved-alerts')).toBeChecked(); - await expect(page.getByTestId('slack-icon-url')).toHaveValue(slackIconURL); - await expect(page.getByTestId('slack-icon-emoji')).toBeHidden(); - await expect(page.getByTestId('slack-username')).toHaveValue(slackUsername); - await expect(page.getByTestId('slack-link-names')).toBeChecked(); - }); - - await test.step('Verify YAML has correct global and receiver config', async () => { - await expect(async () => { - await alertmanager.navigateToYAMLPage(); - const yamlContent = await alertmanager.getYAMLContent(); - const configs = getGlobalsAndReceiverConfig(receiverName, configName, yamlContent); - - expect(configs.globals.slack_api_url).toBe(slackAPIURL); - expect(configs.receiverConfig).not.toHaveProperty('api_url'); - expect(configs.receiverConfig.channel).toBe('myslackchannel'); - expect(configs.receiverConfig.send_resolved).toBe(true); - expect(configs.receiverConfig.icon_url).toBe(slackIconURL); - expect(configs.receiverConfig.username).toBe(slackUsername); - expect(configs.receiverConfig.link_names).toBe(true); - }).toPass({ intervals: [2_000, 3_000, 5_000], timeout: 30_000 }); - }); - }); - - test('creates and edits PagerDuty Receiver correctly', async ({ page }) => { - test.setTimeout(180_000); - - const receiverName = `PagerDutyReceiver-${Date.now()}`; - const configName = 'pagerduty_configs'; - const severity = 'severity'; - const label = `${severity} = warning`; - const pagerDutyClient = '{{ template "pagerduty.default.client" . }}'; - const pagerDutyClientURL = '{{ template "pagerduty.default.clientURL" . }}'; - const pagerDutyURL1 = 'http://pagerduty-url-specific-to-receiver'; - const pagerDutyURL2 = 'http://global-pagerduty-url'; - const pagerDutyURL3 = 'http://pagerduty-url-specific-to-receiver'; - const clientURL = 'http://updated-client-url'; - const pagerDutyDescription = 'new description'; - - await test.step('Create PagerDuty Receiver with basic configuration', async () => { - await alertmanager.navigateToAlertmanager(); - await alertmanager.createReceiver(receiverName, configName); - - await page.getByTestId('integration-key').fill(''); - - // Verify default URL - await expect(page.getByTestId('pagerduty-url')).toHaveValue( - 'https://events.pagerduty.com/v2/enqueue', - ); - - // Check advanced configuration defaults - await alertmanager.showAdvancedConfiguration(); - await expect(page.getByTestId('send-resolved-alerts')).toBeChecked(); - await expect(page.getByTestId('pagerduty-client')).toHaveValue(pagerDutyClient); - await expect(page.getByTestId('pagerduty-client-url')).toHaveValue(pagerDutyClientURL); - await expect(page.getByTestId('pagerduty-description')).toHaveValue( - '{{ template "pagerduty.default.description" .}}', - ); - await expect(page.getByTestId('pagerduty-severity')).toHaveValue('error'); - - await page.getByTestId('label-0').fill(label); - await alertmanager.save(); - }); - - await test.step('Verify PagerDuty Receiver was created correctly', async () => { - await alertmanager.validateReceiverInList(receiverName); - }); - - await test.step('Update pagerduty_url', async () => { - await alertmanager.navigateToEditReceiver(receiverName); - - // Save as default checkbox disabled when url equals global url - await expect(page.getByTestId('save-as-default')).toBeDisabled(); - - // Changing url enables Save as default checkbox - const urlInput = page.getByTestId('pagerduty-url'); - await urlInput.clear(); - await urlInput.fill(pagerDutyURL1); - - await expect(page.getByTestId('save-as-default')).toBeEnabled(); - await alertmanager.save(); - }); - - await test.step('Verify pagerduty_url was saved with Receiver and not global', async () => { - await expect(async () => { - await alertmanager.navigateToYAMLPage(); - const yamlContent = await alertmanager.getYAMLContent(); - const configs = getGlobalsAndReceiverConfig(receiverName, configName, yamlContent); - - expect(configs.globals).not.toHaveProperty('pagerduty_url'); - expect(configs.receiverConfig.url).toBe(pagerDutyURL1); - }).toPass({ intervals: [2_000, 3_000, 5_000], timeout: 30_000 }); - }); - - await test.step('Save pagerduty_url as global', async () => { - await alertmanager.navigateToEditReceiver(receiverName); - - const urlInput = page.getByTestId('pagerduty-url'); - await urlInput.clear(); - await urlInput.fill(pagerDutyURL2); - - const saveAsDefaultCheckbox = page.getByTestId('save-as-default'); - await expect(saveAsDefaultCheckbox).toBeEnabled(); - await saveAsDefaultCheckbox.check(); - - await alertmanager.save(); - }); - - await test.step('Verify pagerduty_url was saved as global', async () => { - await expect(async () => { - await alertmanager.navigateToYAMLPage(); - const yamlContent = await alertmanager.getYAMLContent(); - const configs = getGlobalsAndReceiverConfig(receiverName, configName, yamlContent); - - expect(configs.globals.pagerduty_url).toBe(pagerDutyURL2); - expect(configs.receiverConfig).not.toHaveProperty('url'); - }).toPass({ intervals: [2_000, 3_000, 5_000], timeout: 30_000 }); - }); - - await test.step('Add pagerduty_url to receiver with existing global', async () => { - await alertmanager.navigateToEditReceiver(receiverName); - - const urlInput = page.getByTestId('pagerduty-url'); - await urlInput.clear(); - await urlInput.fill(pagerDutyURL3); - - const saveAsDefaultCheckbox = page.getByTestId('save-as-default'); - await expect(saveAsDefaultCheckbox).toBeEnabled(); - await expect(saveAsDefaultCheckbox).not.toBeChecked(); - - await alertmanager.save(); - }); - - await test.step( - 'Verify pagerduty_url saved with Receiver and global still exists', - async () => { - await expect(async () => { - await alertmanager.navigateToYAMLPage(); - const yamlContent = await alertmanager.getYAMLContent(); - const configs = getGlobalsAndReceiverConfig(receiverName, configName, yamlContent); - - expect(configs.globals.pagerduty_url).toBe(pagerDutyURL2); - expect(configs.receiverConfig.url).toBe(pagerDutyURL3); - }).toPass({ intervals: [2_000, 3_000, 5_000], timeout: 30_000 }); - }, - ); - - await test.step('Update advanced configuration fields', async () => { - await alertmanager.navigateToEditReceiver(receiverName); - await alertmanager.showAdvancedConfiguration(); - - const sendResolvedCheckbox = page.getByTestId('send-resolved-alerts'); - await expect(sendResolvedCheckbox).toBeChecked(); - await sendResolvedCheckbox.uncheck(); - await expect(sendResolvedCheckbox).not.toBeChecked(); - - await page.getByTestId('pagerduty-client').clear(); - await page.getByTestId('pagerduty-client').fill('updated-client'); - - await page.getByTestId('pagerduty-client-url').clear(); - await page.getByTestId('pagerduty-client-url').fill(clientURL); - - await alertmanager.save(); - }); - - await test.step('Verify changed fields are saved with Receiver', async () => { - await expect(async () => { - await alertmanager.navigateToYAMLPage(); - const yamlContent = await alertmanager.getYAMLContent(); - const configs = getGlobalsAndReceiverConfig(receiverName, configName, yamlContent); - - expect(configs.receiverConfig.send_resolved).toBe(false); - expect(configs.receiverConfig.client).toBe('updated-client'); - expect(configs.receiverConfig.client_url).toBe('http://updated-client-url'); - expect(configs.receiverConfig.description).toBeUndefined(); - expect(configs.receiverConfig.severity).toBeUndefined(); - }).toPass({ intervals: [2_000, 3_000, 5_000], timeout: 30_000 }); - }); - - await test.step('Restore defaults, change desc and severity', async () => { - await alertmanager.navigateToEditReceiver(receiverName); - await alertmanager.showAdvancedConfiguration(); - - const sendResolvedCheckbox = page.getByTestId('send-resolved-alerts'); - await expect(sendResolvedCheckbox).not.toBeChecked(); - await sendResolvedCheckbox.check(); - await expect(sendResolvedCheckbox).toBeChecked(); - - await page.getByTestId('pagerduty-client').clear(); - await page.getByTestId('pagerduty-client').fill(pagerDutyClient); - - await page.getByTestId('pagerduty-client-url').clear(); - await page.getByTestId('pagerduty-client-url').fill(pagerDutyClientURL); - - await page.getByTestId('pagerduty-description').clear(); - await page.getByTestId('pagerduty-description').fill(pagerDutyDescription); - - await page.getByTestId('pagerduty-severity').clear(); - await page.getByTestId('pagerduty-severity').fill(severity); - - await alertmanager.save(); - }); - - await test.step('Verify defaults removed from config, desc and severity saved', async () => { - await expect(async () => { - await alertmanager.navigateToYAMLPage(); - const yamlContent = await alertmanager.getYAMLContent(); - const configs = getGlobalsAndReceiverConfig(receiverName, configName, yamlContent); - - expect(configs.receiverConfig.send_resolved).toBeUndefined(); - expect(configs.receiverConfig.client).toBeUndefined(); - expect(configs.receiverConfig.client_url).toBeUndefined(); - expect(configs.receiverConfig.description).toBe(pagerDutyDescription); - expect(configs.receiverConfig.severity).toBe(severity); - }).toPass({ intervals: [2_000, 3_000, 5_000], timeout: 30_000 }); - }); - }); -}); diff --git a/frontend/e2e/tests/console/crud/other-routes.spec.ts b/frontend/e2e/tests/console/crud/other-routes.spec.ts index de29ce37ebf..a1c702cdfee 100644 --- a/frontend/e2e/tests/console/crud/other-routes.spec.ts +++ b/frontend/e2e/tests/console/crud/other-routes.spec.ts @@ -17,7 +17,7 @@ const routes: RouteConfig[] = [ { path: '/', assertLoaded: async (page) => { - await expect(page.getByTestId('page-heading').locator('h1')).toBeAttached(); + await expect(page.getByTestId('page-heading').locator('h1')).toBeVisible(); for (const skeleton of await page.getByTestId('skeleton-chart').all()) { await expect(skeleton).toBeHidden(); } @@ -26,7 +26,7 @@ const routes: RouteConfig[] = [ { path: '/k8s/cluster/clusterroles/view', assertLoaded: async (page) => { - await expect(page.getByTestId('page-heading').locator('h1')).toBeAttached(); + await expect(page.getByTestId('page-heading').locator('h1')).toBeVisible(); }, }, { @@ -60,7 +60,7 @@ const routes: RouteConfig[] = [ { path: '/api-resource/ns/default/core~v1~Pod/schema', assertLoaded: async (page) => { - await expect(page.getByTestId('resource-sidebar-item').first()).toBeAttached(); + await expect(page.getByTestId('resource-sidebar-item').first()).toBeVisible(); }, }, { @@ -123,7 +123,7 @@ const routes: RouteConfig[] = [ { path: '/settings/cluster', assertLoaded: async (page) => { - await expect(page.getByTestId('cluster-version')).toBeAttached(); + await expect(page.getByTestId('cluster-version')).toBeVisible(); }, }, { @@ -152,6 +152,22 @@ test.describe('Visiting other routes', { tag: ['@admin', '@smoke'] }, () => { }); test.describe('Perspective query parameters', { tag: ['@admin'] }, () => { + let patchedPerspectives = false; + + test.afterAll(async ({ k8sClient }) => { + if (patchedPerspectives) { + await k8sClient.customObjectsApi + .patchClusterCustomObject({ + group: 'operator.openshift.io', + version: 'v1', + plural: 'consoles', + name: 'cluster', + body: [{ op: 'remove', path: '/spec/customization/perspectives' }], + }) + .catch(() => {}); + } + }); + test('Developer query parameter switches to Developer perspective', async ({ page, k8sClient, @@ -178,6 +194,7 @@ test.describe('Perspective query parameters', { tag: ['@admin'] }, () => { }, ], }); + patchedPerspectives = true; } await expect(async () => { await page.reload(); diff --git a/frontend/e2e/tests/topology/topology-ci.spec.ts b/frontend/e2e/tests/topology/topology-ci.spec.ts index b51cc969895..a58b70cc4e4 100644 --- a/frontend/e2e/tests/topology/topology-ci.spec.ts +++ b/frontend/e2e/tests/topology/topology-ci.spec.ts @@ -137,6 +137,7 @@ test.describe('Perform actions on topology', { tag: ['@smoke'] }, () => { }); test('Build the application from topology page', async ({ page }) => { + test.setTimeout(300_000); const topology = new TopologyPage(page); await createWorkload(page, 'dotnet-build-test'); @@ -147,6 +148,7 @@ test.describe('Perform actions on topology', { tag: ['@smoke'] }, () => { }); test('Edit workload application groupings: T-09-TC01', async ({ page }) => { + test.setTimeout(300_000); const topology = new TopologyPage(page); await createWorkload(page, 'dotnet-edit-test'); @@ -185,6 +187,7 @@ test.describe('Perform actions on topology', { tag: ['@smoke'] }, () => { }); test('Default state of Display dropdown: T-16-TC01', async ({ page }) => { + test.setTimeout(300_000); const topology = new TopologyPage(page); await createWorkload(page, 'dotnet-display-test'); @@ -202,6 +205,7 @@ test.describe('Perform actions on topology', { tag: ['@smoke'] }, () => { }); test('Delete workload via Action menu: T-15-TC01', async ({ page }) => { + test.setTimeout(300_000); const topology = new TopologyPage(page); const sidebar = new TopologySidebarPage(page); await createWorkload(page, 'dotnet-delete-test'); diff --git a/frontend/e2e/utils/a11y.ts b/frontend/e2e/utils/a11y.ts index f2959ad4bd6..fdd68fa3b37 100644 --- a/frontend/e2e/utils/a11y.ts +++ b/frontend/e2e/utils/a11y.ts @@ -6,6 +6,18 @@ import type { Result } from 'axe-core'; const INCLUDED_IMPACTS = new Set(['serious', 'critical']); +const LOADING_SELECTORS = [ + '.pf-v6-c-spinner', + '.pf-v5-c-spinner', + '.pf-c-spinner', + '.co-m-loader', + '[data-test="loading-indicator"]', + '[data-test="loading-box"]', + '.loading-skeleton', + '.skeleton-catalog--grid', + '[class*="skeleton"]', +].join(', '); + function formatViolations(violations: Result[], target: string): string { const lines: string[] = [ `${violations.length} accessibility violation${violations.length === 1 ? '' : 's'} ${violations.length === 1 ? 'was' : 'were'} detected for ${target}:`, @@ -34,6 +46,17 @@ function formatViolations(violations: Result[], target: string): string { } export async function testA11y(page: Page, target: string, selector?: string): Promise { + try { + const loading = page.locator(LOADING_SELECTORS); + const count = await loading.count().catch(() => 0); + if (count > 0) { + // eslint-disable-next-line no-restricted-syntax + await loading.first().waitFor({ state: 'hidden', timeout: 5_000 }); + } + } catch { + // Loading indicators may have already disappeared — continue + } + let builder = new AxeBuilder({ page }).disableRules('color-contrast'); if (selector) { From ba3e4ebbaf0dbbc00e6f881d1f63cbd2bb9bdc7b Mon Sep 17 00:00:00 2001 From: Robb Hamilton Date: Wed, 29 Jul 2026 14:17:42 -0400 Subject: [PATCH 2/4] CONSOLE-5196: Fix poll-console-updates flake with explicit response wait After swapping the route handler, explicitly wait for the next check-updates response before asserting the toast. This eliminates the race where the 15s polling interval drifts under CI load and the toast assertion times out before the component receives the new data. Co-Authored-By: Claude Opus 4.6 --- frontend/e2e/tests/console/app/poll-console-updates.spec.ts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/frontend/e2e/tests/console/app/poll-console-updates.spec.ts b/frontend/e2e/tests/console/app/poll-console-updates.spec.ts index 69859f9678a..244db3a1dd4 100644 --- a/frontend/e2e/tests/console/app/poll-console-updates.spec.ts +++ b/frontend/e2e/tests/console/app/poll-console-updates.spec.ts @@ -87,6 +87,9 @@ test.describe('PollConsoleUpdates', { tag: ['@admin'] }, () => { await navigateAndWaitForInit(page); updates.setHandler((route) => route.fulfill({ json: UPDATES_NEW_COMMIT })); + await page.waitForResponse((resp) => resp.url().includes('/api/check-updates'), { + timeout: 30_000, + }); await expect(page.getByTestId('refresh-web-console')).toBeVisible({ timeout: 60_000 }); }); @@ -102,6 +105,9 @@ test.describe('PollConsoleUpdates', { tag: ['@admin'] }, () => { await navigateAndWaitForInit(page); updates.setHandler((route) => route.fulfill({ json: UPDATES_NEW_PLUGIN })); + await page.waitForResponse((resp) => resp.url().includes('/api/check-updates'), { + timeout: 30_000, + }); await expect(page.getByTestId('refresh-web-console')).not.toBeAttached({ timeout: 30_000, From dee182d86ef069b73fb57009ba9d8c72eb5776c9 Mon Sep 17 00:00:00 2001 From: Robb Hamilton Date: Wed, 29 Jul 2026 14:18:45 -0400 Subject: [PATCH 3/4] CONSOLE-5196: Use waitFor instead of isVisible for dropdown detection locator.isVisible() returns immediately and ignores the timeout parameter. Use waitFor({ state: 'visible' }) to actually wait up to 2s for the application dropdown to render. Co-Authored-By: Claude Opus 4.6 --- frontend/e2e/pages/topology-page.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/frontend/e2e/pages/topology-page.ts b/frontend/e2e/pages/topology-page.ts index 5cbd5e86a9c..8addf7f779d 100644 --- a/frontend/e2e/pages/topology-page.ts +++ b/frontend/e2e/pages/topology-page.ts @@ -194,7 +194,9 @@ export class TopologyPage extends BasePage { } async fillApplicationName(appName: string): Promise { - if (await this.applicationDropdown.isVisible({ timeout: 2_000 }).catch(() => false)) { + // eslint-disable-next-line no-restricted-syntax + const hasDropdown = await this.applicationDropdown.waitFor({ state: 'visible', timeout: 2_000 }).then(() => true).catch(() => false); + if (hasDropdown) { await this.applicationDropdown.click(); await this.page.getByRole('option', { name: 'Create application' }).click(); } From cde5724af1ba45e401410d4429e63ab95b36d2a6 Mon Sep 17 00:00:00 2001 From: Robb Hamilton Date: Thu, 30 Jul 2026 09:14:00 -0400 Subject: [PATCH 4/4] CONSOLE-5196: Clean up a11y test utility Co-Authored-By: Claude Opus 4.6 --- frontend/e2e/utils/a11y.ts | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/frontend/e2e/utils/a11y.ts b/frontend/e2e/utils/a11y.ts index fdd68fa3b37..04d6fe736ff 100644 --- a/frontend/e2e/utils/a11y.ts +++ b/frontend/e2e/utils/a11y.ts @@ -8,7 +8,6 @@ const INCLUDED_IMPACTS = new Set(['serious', 'critical']); const LOADING_SELECTORS = [ '.pf-v6-c-spinner', - '.pf-v5-c-spinner', '.pf-c-spinner', '.co-m-loader', '[data-test="loading-indicator"]', @@ -20,7 +19,9 @@ const LOADING_SELECTORS = [ function formatViolations(violations: Result[], target: string): string { const lines: string[] = [ - `${violations.length} accessibility violation${violations.length === 1 ? '' : 's'} ${violations.length === 1 ? 'was' : 'were'} detected for ${target}:`, + `${violations.length} accessibility violation${violations.length === 1 ? '' : 's'} ${ + violations.length === 1 ? 'was' : 'were' + } detected for ${target}:`, ]; violations.forEach((violation, index) => { @@ -54,7 +55,7 @@ export async function testA11y(page: Page, target: string, selector?: string): P await loading.first().waitFor({ state: 'hidden', timeout: 5_000 }); } } catch { - // Loading indicators may have already disappeared — continue + // Loading indicators may have already disappeared - continue } let builder = new AxeBuilder({ page }).disableRules('color-contrast');