This is an automated email from the ASF dual-hosted git repository.

tbonelee pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/zeppelin.git


The following commit(s) were added to refs/heads/master by this push:
     new 23483c69df [MINOR] Remove orphaned login modal backdrops before 
driving the navbar in Selenium tests
23483c69df is described below

commit 23483c69df74e6c5892a5b21ef3213e1c2121973
Author: YONGJAE LEE (이용재) <[email protected]>
AuthorDate: Sun Sep 27 14:49:08 2026 +0900

    [MINOR] Remove orphaned login modal backdrops before driving the navbar in 
Selenium tests
    
    ### What is this PR for?
    After ZEPPELIN-6711, `AuthenticationIT.testSimpleAuthentication` still 
fails intermittently in `test-selenium-with-spark-module-for-spark-3-5`. 
`#loginModal` is already hidden, but when `logoutUser` clicks the navbar user 
menu, `<div class="modal-backdrop fade">` intercepts the click.
    
    Bootstrap 3.4.1 removes a backdrop through `this.$backdrop`. When the login 
modal is shown and hidden again while an earlier transition is still running, 
that reference is replaced and the earlier backdrop is never removed. It stays 
over the page at opacity 0 and intercepts every click, so the existing retry in 
`logoutUser` fails for the same reason.
    
    `dismissLoginModal()` now removes `.modal-backdrop` elements that no 
displayed modal owns once the login modal is closed. An intercepted navbar 
click also logs the modal and backdrop state. The app-side behaviour that 
leaves the backdrop behind is not addressed here.
    
    ### What type of PR is it?
    Bug Fix
    
    ### Todos
    * [x] Remove backdrops that no displayed modal owns
    * [x] Log the modal state when the navbar click is intercepted
    
    ### What is the Jira issue?
    
    ### How should this be tested?
    ```bash
    ./mvnw clean install -DskipTests -am -pl zeppelin-integration -Pweb-classic 
-Pintegration -Pspark-scala-2.12 -Pspark-3.5 -Pweb-dist
    ./mvnw verify -pl zeppelin-integration -Dit.test=AuthenticationIT 
-Dtest=none -Dsurefire.failIfNoSpecifiedTests=false -Pweb-classic -Pintegration 
-Pspark-scala-2.12 -Pspark-3.5 -Pweb-dist -Pusing-source-tree
    ```
    
    I ran only `AuthenticationIT` on a fork, with the same build as the CI job:
    
    | Variant | Runs | Pass | Fail |
    |---|---|---|---|
    | master + diagnostics only | 30 | 17 | 13 |
    | this PR | 100 | 100 | 0 |
    
    All 13 failures logged the same state: `loginModal=modal fade ng-scope 
display=none, backdrops=[modal-backdrop fade]`. With this PR, 44 of the 100 
runs logged and removed such a backdrop, and all passed.
    
    ### Screenshots (if appropriate)
    N/A
    
    ### Questions:
    * Does the license files need to update? No
    * Is there breaking changes for older versions? No
    * Does this needs documentation? No
    
    
    Closes #5502 from voidmatcha/fix/login-modal-orphan-backdrop.
    
    Signed-off-by: ChanHo Lee <[email protected]>
---
 .../org/apache/zeppelin/AbstractZeppelinIT.java    | 42 +++++++++++++++++++++-
 1 file changed, 41 insertions(+), 1 deletion(-)

diff --git 
a/zeppelin-integration/src/test/java/org/apache/zeppelin/AbstractZeppelinIT.java
 
b/zeppelin-integration/src/test/java/org/apache/zeppelin/AbstractZeppelinIT.java
index 6b07cc2b77..56424126ee 100644
--- 
a/zeppelin-integration/src/test/java/org/apache/zeppelin/AbstractZeppelinIT.java
+++ 
b/zeppelin-integration/src/test/java/org/apache/zeppelin/AbstractZeppelinIT.java
@@ -116,6 +116,7 @@ abstract public class AbstractZeppelinIT {
    */
   private void dismissLoginModal() {
     if (loginModalClosed(MODAL_CLOSE_TIMEOUT_SEC)) {
+      removeOrphanedModalBackdrops();
       return;
     }
     LOGGER.warn("Login modal still displayed after {}s, taking it down from 
the page",
@@ -131,6 +132,44 @@ abstract public class AbstractZeppelinIT {
     }
   }
 
+  // Bootstrap 3 removes a backdrop through this.$backdrop,
+  // so reopening the modal mid-transition orphans the earlier backdrop, which 
blocks every click.
+  // A hidden modal can keep its "in" class, so only a displayed modal owns a 
backdrop.
+  private void removeOrphanedModalBackdrops() {
+    Object removed = ((JavascriptExecutor) 
manager.getWebDriver()).executeScript(
+        "var backdrops = document.querySelectorAll('.modal-backdrop');"
+            + "var modals = document.querySelectorAll('.modal.in');"
+            + "for (var m = 0; m < modals.length; m++) {"
+            + "  if (window.getComputedStyle(modals[m]).display !== 'none') {"
+            + "    return null;"
+            + "  }"
+            + "}"
+            + "if (backdrops.length === 0) {"
+            + "  return null;"
+            + "}"
+            + "var classes = [];"
+            + "for (var i = 0; i < backdrops.length; i++) {"
+            + "  classes.push(backdrops[i].className);"
+            + "  backdrops[i].parentNode.removeChild(backdrops[i]);"
+            + "}"
+            + "return classes.join(', ');");
+    if (removed != null) {
+      LOGGER.warn("Removed modal backdrops left behind after the login modal 
closed: {}", removed);
+    }
+  }
+
+  private String describeModalState() {
+    Object state = ((JavascriptExecutor) manager.getWebDriver()).executeScript(
+        "var modal = document.getElementById('loginModal');"
+            + "var backdrops = document.querySelectorAll('.modal-backdrop');"
+            + "var classes = [];"
+            + "for (var i = 0; i < backdrops.length; i++) { 
classes.push(backdrops[i].className); }"
+            + "var modalState = modal"
+            + "    ? modal.className + ' display=' + modal.style.display : 
'absent';"
+            + "return 'loginModal=' + modalState + ', backdrops=[' + 
classes.join(', ') + ']';");
+    return String.valueOf(state);
+  }
+
   /** Returns true once the login modal is hidden or gone, false if it is 
still displayed. */
   private boolean loginModalClosed(final long timeWait) {
     try {
@@ -213,7 +252,8 @@ abstract public class AbstractZeppelinIT {
       // login.controller.js re-opens it one second later, so it can appear 
between the
       // wait above and this click. An intercepted click never reached the 
menu, so the
       // dropdown is still closed and opening it again is safe.
-      LOGGER.warn("Navbar user menu click was intercepted, retrying once", e);
+      LOGGER.warn("Navbar user menu click was intercepted ({}), retrying once",
+          describeModalState(), e);
       dismissLoginModal();
       clickableWait(userMenu, MAX_BROWSER_TIMEOUT_SEC).click();
     }

Reply via email to