From 531237bb6ce0a04a3a539fde59527c8f5d07d977 Mon Sep 17 00:00:00 2001 From: YONGJAE LEE Date: Sat, 26 Sep 2026 06:17:33 +0900 Subject: [PATCH] [MINOR] Remove orphaned login modal backdrops before driving the navbar in Selenium tests --- .../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 6b07cc2b777..56424126ee7 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 @@ protected void authenticationUser(String userName, String password) { */ 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 @@ private void dismissLoginModal() { } } + // 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 @@ protected void logoutUser(String userName) throws URISyntaxException { // 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(); }