From 751f7571f811d784e74bc5a2b1ddadb7499a61ca Mon Sep 17 00:00:00 2001 From: Cody Maffucci <46459665+Maffooch@users.noreply.github.com> Date: Wed, 17 Jun 2026 21:55:12 -0600 Subject: [PATCH] Stabilize flaky notification-webhook integration test The Selenium test pinged the public httpbin.org on webhook save (a 10s synchronous validation request), making it fail intermittently in CI. Route the webhook URL to the local go-httpbin mock (already used by the unit tests) by wiring webhook.endpoint into the integration-test stack and the workflow's explicit service startup. Also replace the tautological "or Webhook" assertions with explicit alert waits and specific success-message checks so real failures actually fail. Co-Authored-By: Claude Opus 4.8 (1M context) --- .github/workflows/integration-tests.yml | 2 +- docker-compose.override.integration_tests.yml | 2 + tests/notification_webhook_test.py | 70 ++++++++++++------- 3 files changed, 46 insertions(+), 28 deletions(-) diff --git a/.github/workflows/integration-tests.yml b/.github/workflows/integration-tests.yml index a2d6b9d051f..96eaf7a33b4 100644 --- a/.github/workflows/integration-tests.yml +++ b/.github/workflows/integration-tests.yml @@ -104,7 +104,7 @@ jobs: run: ln -s docker-compose.override.integration_tests.yml docker-compose.override.yml - name: Start Dojo - run: docker compose up --no-deps -d postgres nginx celerybeat celeryworker mailhog uwsgi valkey + run: docker compose up --no-deps -d postgres nginx celerybeat celeryworker mailhog uwsgi valkey webhook.endpoint env: DJANGO_VERSION: ${{ matrix.os }} NGINX_VERSION: alpine diff --git a/docker-compose.override.integration_tests.yml b/docker-compose.override.integration_tests.yml index 611becefbec..71085eedd68 100644 --- a/docker-compose.override.integration_tests.yml +++ b/docker-compose.override.integration_tests.yml @@ -72,6 +72,8 @@ services: published: 1025 protocol: tcp mode: host + "webhook.endpoint": + image: mccutchen/go-httpbin:2.18.3@sha256:3992f3763e9ce5a4307eae0a869a78b4df3931dc8feba74ab823dd2444af6a6b volumes: defectdojo_postgres_integration_tests: {} defectdojo_media_integration_tests: {} diff --git a/tests/notification_webhook_test.py b/tests/notification_webhook_test.py index f5e9f202aa5..71daaa0ad45 100644 --- a/tests/notification_webhook_test.py +++ b/tests/notification_webhook_test.py @@ -3,10 +3,27 @@ from base_test_class import BaseTestCase, on_exception_html_source_logger from selenium.webdriver.common.by import By +from selenium.webdriver.support import expected_conditions +from selenium.webdriver.support.ui import WebDriverWait + +# Local go-httpbin mock wired into the integration-test stack (see +# docker-compose.override.integration_tests.yml). DefectDojo pings this URL +# synchronously when a webhook is saved, so it must resolve from the uwsgi +# container. Never point this at a public service (e.g. httpbin.org): that adds +# an external network dependency and makes this test flaky. +WEBHOOK_ENDPOINT_URL = "http://webhook.endpoint:8080/post" class NotificationWebhookTest(BaseTestCase): + def wait_for_alert(self): + """Wait for a Bootstrap alert to render after a form submit.""" + WebDriverWait(self.driver, 30).until( + expected_conditions.presence_of_element_located( + (By.CSS_SELECTOR, ".alert-success, .alert-danger"), + ), + ) + @on_exception_html_source_logger def test_enable_webhook_notifications(self): """Enable webhook notifications in system settings.""" @@ -22,10 +39,7 @@ def test_enable_webhook_notifications(self): def test_list_webhooks_page_loads(self): driver = self.driver driver.get(self.base_url + "notifications/webhooks") - self.assertTrue( - self.is_text_present_on_page(text="Notification Webhook") - or self.is_text_present_on_page(text="Webhook"), - ) + self.assertTrue(self.is_text_present_on_page(text="Webhook")) @on_exception_html_source_logger def test_add_notification_webhook(self): @@ -34,13 +48,13 @@ def test_add_notification_webhook(self): driver.find_element(By.ID, "id_name").clear() driver.find_element(By.ID, "id_name").send_keys("Test Webhook") driver.find_element(By.ID, "id_url").clear() - driver.find_element(By.ID, "id_url").send_keys("https://httpbin.org/post") + driver.find_element(By.ID, "id_url").send_keys(WEBHOOK_ENDPOINT_URL) driver.find_element(By.CSS_SELECTOR, "input.btn.btn-primary").click() - self.assertTrue( - self.is_text_present_on_page(text="Test Webhook") - or self.is_text_present_on_page(text="Webhook"), - ) + self.wait_for_alert() + self.assertFalse(self.is_error_message_present()) + self.assertTrue(self.is_success_message_present(text="Notification Webhook added successfully.")) + self.assertTrue(self.is_text_present_on_page(text="Test Webhook")) @on_exception_html_source_logger def test_edit_notification_webhook(self): @@ -48,17 +62,20 @@ def test_edit_notification_webhook(self): driver.get(self.base_url + "notifications/webhooks") # Click Edit link from the webhooks list (link text is "Edit / activate / deactivate") edit_links = driver.find_elements(By.CSS_SELECTOR, "a.btn.btn-warning") - if len(edit_links) > 0: - edit_links[0].click() - driver.find_element(By.ID, "id_name").clear() - driver.find_element(By.ID, "id_name").send_keys("Edited Test Webhook") - driver.find_element(By.CSS_SELECTOR, "input.btn.btn-primary").click() - self.assertTrue( - self.is_text_present_on_page(text="Edited Test Webhook") - or self.is_text_present_on_page(text="Webhook"), - ) - else: + if len(edit_links) == 0: self.fail("No Edit link found for webhook") + edit_links[0].click() + driver.find_element(By.ID, "id_name").clear() + driver.find_element(By.ID, "id_name").send_keys("Edited Test Webhook") + # Ensure the endpoint stays pointed at the local mock so the save-time ping succeeds. + driver.find_element(By.ID, "id_url").clear() + driver.find_element(By.ID, "id_url").send_keys(WEBHOOK_ENDPOINT_URL) + driver.find_element(By.CSS_SELECTOR, "input.btn.btn-primary").click() + + self.wait_for_alert() + self.assertFalse(self.is_error_message_present()) + self.assertTrue(self.is_success_message_present(text="Notification Webhook updated successfully.")) + self.assertTrue(self.is_text_present_on_page(text="Edited Test Webhook")) @on_exception_html_source_logger def test_delete_notification_webhook(self): @@ -66,15 +83,14 @@ def test_delete_notification_webhook(self): driver.get(self.base_url + "notifications/webhooks") # Click Delete link from the webhooks list delete_links = driver.find_elements(By.CSS_SELECTOR, "a.btn.btn-danger") - if len(delete_links) > 0: - delete_links[0].click() - driver.find_element(By.CSS_SELECTOR, "input.btn.btn-danger").click() - self.assertTrue( - self.is_text_present_on_page(text="Webhook") - or not self.is_error_message_present(), - ) - else: + if len(delete_links) == 0: self.fail("No Delete link found for webhook") + delete_links[0].click() + driver.find_element(By.CSS_SELECTOR, "input.btn.btn-danger").click() + + self.wait_for_alert() + self.assertFalse(self.is_error_message_present()) + self.assertTrue(self.is_success_message_present(text="Notification Webhook deleted successfully.")) @on_exception_html_source_logger def test_disable_webhook_notifications(self):