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

hansva pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/hop.git


The following commit(s) were added to refs/heads/main by this push:
     new b70fc0ba72 [IT] fix selenium tests (#8399)
b70fc0ba72 is described below

commit b70fc0ba723c763a6f75d87b22ca7249d59625e1
Author: Hans Van Akelyen <[email protected]>
AuthorDate: Tue Sep 15 16:08:35 2026 +0200

    [IT] fix selenium tests (#8399)
---
 web-tests/pom.xml                                  |  3 +-
 .../org/apache/hop/web/it/HopWebEnvironment.java   | 31 ++++++++---
 .../org/apache/hop/web/it/pages/ContextDialog.java |  8 +--
 .../org/apache/hop/web/it/pages/HopGuiPage.java    | 65 ++++++++++++----------
 .../apache/hop/web/it/pages/PreviewDataDialog.java |  6 +-
 5 files changed, 66 insertions(+), 47 deletions(-)

diff --git a/web-tests/pom.xml b/web-tests/pom.xml
index 33fcab02ce..d2ed463124 100644
--- a/web-tests/pom.xml
+++ b/web-tests/pom.xml
@@ -31,7 +31,8 @@
 
     <properties>
         <hopweb.browser>auto</hopweb.browser>
-        
<hopweb.browserImage>selenium/standalone-chrome:4.47.0</hopweb.browserImage>
+        <!-- Empty: HopWebEnvironment picks selenium/standalone-chrome or 
-chromium by architecture -->
+        <hopweb.browserImage></hopweb.browserImage>
         <hopweb.build>false</hopweb.build>
         <hopweb.headless>true</hopweb.headless>
         <hopweb.image>apache/hop-web:Development</hopweb.image>
diff --git 
a/web-tests/src/test/java/org/apache/hop/web/it/HopWebEnvironment.java 
b/web-tests/src/test/java/org/apache/hop/web/it/HopWebEnvironment.java
index 21802d4346..da2be285a5 100644
--- a/web-tests/src/test/java/org/apache/hop/web/it/HopWebEnvironment.java
+++ b/web-tests/src/test/java/org/apache/hop/web/it/HopWebEnvironment.java
@@ -65,8 +65,10 @@ import org.testcontainers.utility.MountableFile;
  *       ago. Re-pulling an unchanged image transfers nothing.
  *   <li>{@code hopweb.url} - URL of an already running Hop Web. Set it while 
writing tests to skip
  *       container startup entirely, e.g. {@code 
-Dhopweb.url=http://localhost:8080/ui}
- *   <li>{@code hopweb.browser} - {@code auto} (default), {@code container} or 
{@code local}
- *   <li>{@code hopweb.browserImage} - the browser image, for a containerised 
browser
+ *   <li>{@code hopweb.browser} - {@code auto} (default, a container), {@code 
container} or {@code
+ *       local}
+ *   <li>{@code hopweb.browserImage} - the browser image, for a containerised 
browser; defaults to
+ *       selenium/standalone-chrome on amd64 and selenium/standalone-chromium 
on arm64
  *   <li>{@code hopweb.headless} - only meaningful for a local browser
  *   <li>{@code hopweb.javaOptions} - the JVM options Hop Web runs with, 
replacing the image's
  *       {@code HOP_OPTIONS}. The image default is {@code 
-XX:+AggressiveHeap}, which sizes the heap
@@ -87,7 +89,19 @@ public final class HopWebEnvironment {
    * matching image is out; the client and the browser only have to speak the 
same WebDriver
    * protocol, not carry the same version.
    */
-  private static final String DEFAULT_BROWSER_IMAGE = 
"selenium/standalone-chrome:4.47.0";
+  private static final String BROWSER_IMAGE_VERSION = "4.47.0";
+
+  /**
+   * selenium/standalone-chrome is amd64 only; selenium/standalone-chromium is 
the same image built
+   * for arm64 as well. Both run the same browser, so an arm64 machine gets a 
containerised browser
+   * too instead of falling back to whatever Chrome is installed locally - a 
local browser is fast
+   * enough to hide the request-timing races the CI browser trips over.
+   */
+  private static final String DEFAULT_BROWSER_IMAGE =
+      ("aarch64".equals(System.getProperty("os.arch"))
+              ? "selenium/standalone-chromium:"
+              : "selenium/standalone-chrome:")
+          + BROWSER_IMAGE_VERSION;
 
   private static final String HOP_WEB_ALIAS = "hop-web";
   private static final int HOP_WEB_PORT = 8080;
@@ -410,7 +424,10 @@ public final class HopWebEnvironment {
   private WebDriver containerBrowser(Network network) {
     BrowserWebDriverContainer<?> browser =
         new BrowserWebDriverContainer<>(
-                DockerImageName.parse(property("hopweb.browserImage", 
DEFAULT_BROWSER_IMAGE)))
+                DockerImageName.parse(property("hopweb.browserImage", 
DEFAULT_BROWSER_IMAGE))
+                    // Testcontainers only accepts selenium/standalone-chrome 
by name; the
+                    // multi-arch selenium/standalone-chromium is the same 
thing for arm64.
+                    .asCompatibleSubstituteFor("selenium/standalone-chrome"))
             .withNetwork(network)
             .withCapabilities(chromeOptions())
             .withStartupTimeout(Duration.ofSeconds(startupTimeoutSeconds()));
@@ -469,9 +486,9 @@ public final class HopWebEnvironment {
 
     static BrowserMode resolve(String value) {
       if ("auto".equalsIgnoreCase(value)) {
-        // The selenium/standalone-chrome images are amd64 only, so an arm64 
developer machine
-        // has to drive the browser it already has.
-        return "aarch64".equals(System.getProperty("os.arch")) ? LOCAL : 
CONTAINER;
+        // Always the container: it is what CI runs, and the default image is 
picked per
+        // architecture. -Dhopweb.browser=local is there for watching a test 
in a real window.
+        return CONTAINER;
       }
       return "local".equalsIgnoreCase(value) ? LOCAL : CONTAINER;
     }
diff --git 
a/web-tests/src/test/java/org/apache/hop/web/it/pages/ContextDialog.java 
b/web-tests/src/test/java/org/apache/hop/web/it/pages/ContextDialog.java
index 13ed8f4198..ef7f87c3ef 100644
--- a/web-tests/src/test/java/org/apache/hop/web/it/pages/ContextDialog.java
+++ b/web-tests/src/test/java/org/apache/hop/web/it/pages/ContextDialog.java
@@ -50,12 +50,8 @@ public class ContextDialog {
    * immediately, so Enter fired before the results had filtered at all.
    */
   private static final String RENDER_STATE =
-      "const sh=[...document.body.children].filter(d=>{"
-          + "if(d.tagName!=='DIV')return false;"
-          + "const z=parseInt(getComputedStyle(d).zIndex);"
-          + "const r=d.getBoundingClientRect();"
-          + "return z>=100000&&r.width>100&&r.height>100;});"
-          + "const top=sh[sh.length-1];"
+      HopGuiPage.DIALOG_SHELLS
+          + "const top=shells[shells.length-1];"
           + "if(!top)return '';"
           + "const r=top.getBoundingClientRect();"
           + "return 
Math.round(r.width)+'x'+Math.round(r.height)+'|'+(top.innerText||'');";
diff --git 
a/web-tests/src/test/java/org/apache/hop/web/it/pages/HopGuiPage.java 
b/web-tests/src/test/java/org/apache/hop/web/it/pages/HopGuiPage.java
index 80503c86c7..c67f3774c1 100644
--- a/web-tests/src/test/java/org/apache/hop/web/it/pages/HopGuiPage.java
+++ b/web-tests/src/test/java/org/apache/hop/web/it/pages/HopGuiPage.java
@@ -33,18 +33,36 @@ import org.openqa.selenium.support.ui.WebDriverWait;
 public class HopGuiPage {
 
   /**
-   * Every visible RAP shell that is big enough to be a window, outermost 
first. Index 0 is the Hop
-   * GUI itself, so anything beyond it is an open dialog. A shell's first line 
of text is its title,
+   * Script prologue that leaves {@code shells}: the dialogs on screen, 
outermost first.
+   *
+   * <p>A dialog is recognised by what RAP hangs off its element - a {@code 
rwt.widgets.Shell} with
+   * a title bar - rather than by looking like one. The previous rule, "any 
body-level div at
+   * z-index 100000 or more that is bigger than 100x100", also matched the 
transparent full-screen
+   * blocker RAP shows while a request is in flight. On a slow browser that 
blocker is on screen in
+   * the gap between choosing "Edit" in the context dialog and the transform 
dialog opening (the
+   * action runs in an asyncExec, one request later), and it read as a dialog 
with an empty title:
+   * {@code awaitDialog()} returned "", {@code closeAllDialogs()} "closed" it, 
and the real dialog
+   * then opened under the next click. Shells without a title bar are Hop's 
canvas tooltip and the
+   * Hop GUI window itself; a title-bar shell with no parent that covers the 
viewport is also the
+   * GUI window, should it ever get a caption.
+   */
+  public static final String DIALOG_SHELLS =
+      "const shells=[...document.body.children].filter(d=>{"
+          + "const w=d.rwtWidget;"
+          + "if(!w||w.classname!=='rwt.widgets.Shell')return false;"
+          + "if(typeof 
w.hasState!=='function'||!w.hasState('rwt_TITLE'))return false;"
+          + "const r=d.getBoundingClientRect();"
+          + "if(!(r.width>0&&r.height>0))return false;"
+          + "return !!w._parentShell"
+          + "||r.width<window.innerWidth||r.height<window.innerHeight;});";
+
+  /**
+   * The titles of the open dialogs, outermost first. A shell's first line of 
text is its title,
    * which is how these tests identify dialogs - far steadier than the 
positional {@code
    * //body/div[5]/div[1]} the previous suite matched on.
    */
   private static final String SHELL_TITLES =
-      "return [...document.body.children].filter(d=>{"
-          + "if(d.tagName!=='DIV')return false;"
-          + "const z=parseInt(getComputedStyle(d).zIndex);"
-          + "const r=d.getBoundingClientRect();"
-          + "return z>=100000&&r.width>100&&r.height>100;})"
-          + ".map(d=>(d.innerText||'').split('\\n')[0].trim());";
+      DIALOG_SHELLS + "return 
shells.map(d=>(d.innerText||'').split('\\n')[0].trim());";
 
   /**
    * How often to re-check a condition. The default of 500ms is the single 
biggest cost in this
@@ -107,11 +125,7 @@ public class HopGuiPage {
    * and a dialog builds its fields in plain SWT code.
    */
   private static final String TOP_INPUTS =
-      "const shells=[...document.body.children].filter(d=>{"
-          + "if(d.tagName!=='DIV')return false;"
-          + "const z=parseInt(getComputedStyle(d).zIndex);"
-          + "const r=d.getBoundingClientRect();"
-          + "return z>=100000&&r.width>100&&r.height>100;});"
+      DIALOG_SHELLS
           + "const top=shells[shells.length-1]||document;"
           + "return [...top.querySelectorAll('input')]"
           + ".filter(i=>i.type==='text'&&i.offsetParent!==null);";
@@ -138,8 +152,7 @@ public class HopGuiPage {
   public static List<String> openDialogTitles(WebDriver driver) {
     @SuppressWarnings("unchecked")
     List<String> titles = (List<String>) ((JavascriptExecutor) 
driver).executeScript(SHELL_TITLES);
-    // Drop the Hop GUI shell itself; only the dialogs above it are 
interesting.
-    return titles.isEmpty() ? titles : titles.subList(1, titles.size());
+    return titles;
   }
 
   public static String topDialogTitle(WebDriver driver) {
@@ -162,13 +175,7 @@ public class HopGuiPage {
   }
 
   private static final String TOP_SHELL_TEXT =
-      "const shells=[...document.body.children].filter(d=>{"
-          + "if(d.tagName!=='DIV')return false;"
-          + "const z=parseInt(getComputedStyle(d).zIndex);"
-          + "const r=d.getBoundingClientRect();"
-          + "return z>=100000&&r.width>100&&r.height>100;});"
-          + "const top=shells[shells.length-1];"
-          + "return top?top.innerText:'';";
+      DIALOG_SHELLS + "const top=shells[shells.length-1];" + "return 
top?top.innerText:'';";
 
   /**
    * Closes the welcome dialog if this Hop Web was configured to show it. The 
image used by the
@@ -207,7 +214,13 @@ public class HopGuiPage {
    * reading the title straight afterwards is racing it.
    */
   public String awaitDialog() {
-    return wait.until(d -> topDialogTitle());
+    // A blank title is not a dialog: nothing Hop opens is untitled, so it 
would be the detector
+    // catching something else mid-render.
+    return wait.until(
+        d -> {
+          String title = topDialogTitle();
+          return title == null || title.isBlank() ? null : title;
+        });
   }
 
   /** How long to give Escape before falling back to the dialog's own button. 
*/
@@ -354,11 +367,7 @@ public class HopGuiPage {
 
   private static final String FIELD_BESIDE_LABEL =
       "const wanted=arguments[0];"
-          + "const shells=[...document.body.children].filter(d=>{"
-          + "if(d.tagName!=='DIV')return false;"
-          + "const z=parseInt(getComputedStyle(d).zIndex);"
-          + "const r=d.getBoundingClientRect();"
-          + "return z>=100000&&r.width>100&&r.height>100;});"
+          + DIALOG_SHELLS
           + "const top=shells[shells.length-1];"
           + "if(!top)return null;"
           + "const label=[...top.querySelectorAll('div')].find("
diff --git 
a/web-tests/src/test/java/org/apache/hop/web/it/pages/PreviewDataDialog.java 
b/web-tests/src/test/java/org/apache/hop/web/it/pages/PreviewDataDialog.java
index 8d01d33727..e1dc677395 100644
--- a/web-tests/src/test/java/org/apache/hop/web/it/pages/PreviewDataDialog.java
+++ b/web-tests/src/test/java/org/apache/hop/web/it/pages/PreviewDataDialog.java
@@ -44,11 +44,7 @@ public class PreviewDataDialog {
    * left out.
    */
   private static final String GRID_ROWS =
-      "const shells=[...document.body.children].filter(d=>{"
-          + "if(d.tagName!=='DIV')return false;"
-          + "const z=parseInt(getComputedStyle(d).zIndex);"
-          + "const r=d.getBoundingClientRect();"
-          + "return z>=100000&&r.width>100&&r.height>100;});"
+      HopGuiPage.DIALOG_SHELLS
           + "const top=shells[shells.length-1];"
           + "if(!top)return [];"
           + "return [...top.querySelectorAll('div')]"

Reply via email to