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 e93c37ad7d [IT] test hardening (#8395)
e93c37ad7d is described below

commit e93c37ad7d7de6c20f1c9e106ef23a677aed9e6a
Author: Hans Van Akelyen <[email protected]>
AuthorDate: Tue Sep 15 14:27:58 2026 +0200

    [IT] test hardening (#8395)
---
 .../java/org/apache/hop/web/it/HopWebFileTest.java |  4 ++
 .../org/apache/hop/web/it/pages/HopGuiPage.java    | 49 ++++++++++++++++++++++
 .../apache/hop/web/it/pages/PipelineGraphPage.java | 35 +++++++++++++++-
 3 files changed, 87 insertions(+), 1 deletion(-)

diff --git a/web-tests/src/test/java/org/apache/hop/web/it/HopWebFileTest.java 
b/web-tests/src/test/java/org/apache/hop/web/it/HopWebFileTest.java
index 116f20ec56..45743102ee 100644
--- a/web-tests/src/test/java/org/apache/hop/web/it/HopWebFileTest.java
+++ b/web-tests/src/test/java/org/apache/hop/web/it/HopWebFileTest.java
@@ -84,6 +84,10 @@ class HopWebFileTest extends HopWebTestBase {
     // day and the one that stopped working in issue #6362.
     graph.addTransform(SECOND, 150, 0);
     hopGui.clickWidget(HopGuiPage.SAVE_FILE);
+    // Saving clears the changed flag, which disables Save again: that is the 
sign the save went
+    // through. Reloading the browser straight after the click can cut off the 
request that
+    // carries it, and then the file quietly keeps its old contents.
+    hopGui.awaitDisabled(HopGuiPage.SAVE_FILE);
 
     inANewSession();
     PipelineGraphPage reopened = hopGui.openPipeline(path, FIRST);
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 1d8e6fbb9c..80503c86c7 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
@@ -526,13 +526,62 @@ public class HopGuiPage {
                 .orElse(null));
   }
 
+  /**
+   * Whether the RAP widget behind an element takes clicks. RAP does not mark 
a disabled widget in
+   * the DOM at all; the state lives in the client-side widget object it hangs 
off the element, and
+   * a click on a disabled one is dropped there without the server ever 
hearing of it. Elements
+   * without a widget are left alone.
+   *
+   * <p>Compared against {@code false} rather than for {@code true}: the 
property is inheritable and
+   * a widget that was never disabled itself answers with the string {@code 
"inherit"}.
+   */
+  /** Long enough for the asyncExec that enables a toolbar item, short enough 
to read as a bug. */
+  private static final Duration ENABLE_TIMEOUT = Duration.ofSeconds(10);
+
+  private static final String IS_ENABLED =
+      "const w=arguments[0].rwtWidget;"
+          + "return !w||typeof 
w.getEnabled!=='function'||w.getEnabled()!==false;";
+
+  /**
+   * Clicks once the widget is enabled.
+   *
+   * <p>Hop enables toolbar items from an {@code asyncExec} after the change 
that warrants them, so
+   * a test that acts as soon as it sees the change on the canvas can click a 
Save button that is
+   * still disabled and get a silent no-op for it (a file reopened without its 
last transform).
+   * Waiting here turns that into a clear timeout when a button is disabled 
for real.
+   */
   private void click(WebElement element) {
     if (element == null) {
       throw new NoSuchElementException("No element to click");
     }
+    // A wait of its own: withMessage would stick to the shared one.
+    waitFor(driver, ENABLE_TIMEOUT)
+        .withMessage(() -> "widget " + describe(element) + " is disabled")
+        .until(d -> isEnabled(element));
     new Actions(driver).moveToElement(element).click().perform();
   }
 
+  /**
+   * Waits for a widget to be disabled. Hop disables Save once a file is 
saved, so this is how a
+   * test knows a save it clicked has actually happened before it does 
anything drastic, such as
+   * throwing the session away.
+   */
+  public void awaitDisabled(By locator) {
+    WebElement element = visible(locator);
+    waitFor(driver, ENABLE_TIMEOUT)
+        .withMessage(() -> "widget " + describe(element) + " is still enabled")
+        .until(d -> !isEnabled(element));
+  }
+
+  private boolean isEnabled(WebElement element) {
+    return Boolean.TRUE.equals(((JavascriptExecutor) 
driver).executeScript(IS_ENABLED, element));
+  }
+
+  private static String describe(WebElement element) {
+    String id = element.getAttribute("data-hop-id");
+    return id != null ? id : element.getText();
+  }
+
   /**
    * An element whose text is this label.
    *
diff --git 
a/web-tests/src/test/java/org/apache/hop/web/it/pages/PipelineGraphPage.java 
b/web-tests/src/test/java/org/apache/hop/web/it/pages/PipelineGraphPage.java
index 33cfaa0eb2..e4d36d2893 100644
--- a/web-tests/src/test/java/org/apache/hop/web/it/pages/PipelineGraphPage.java
+++ b/web-tests/src/test/java/org/apache/hop/web/it/pages/PipelineGraphPage.java
@@ -300,9 +300,42 @@ public class PipelineGraphPage {
     dialog.choose(action);
   }
 
-  /** Clicks the canvas at an offset from its middle. */
+  /**
+   * Two clicks on the same spot closer together than this are one 
double-click. Chromedriver counts
+   * clicks the way a browser does - by time and position, not by what 
happened in between - and the
+   * browser default is 500 ms.
+   */
+  private static final long DOUBLE_CLICK_WINDOW_MS = 600;
+
+  private long lastClickAt;
+  private int lastClickX;
+  private int lastClickY;
+
+  /**
+   * Clicks the canvas at an offset from its middle.
+   *
+   * <p>Never within the double-click window of the previous click on the same 
spot. Adding a
+   * transform clicks where it will appear and the next thing a test does is 
click the transform, so
+   * with nothing in between but keystrokes the second click can arrive fast 
enough to count as a
+   * double-click - which on a transform means "edit", so its dialog opens 
where the context dialog
+   * was expected.
+   */
   public void clickAt(int offsetX, int offsetY) {
+    if (offsetX == lastClickX && offsetY == lastClickY) {
+      long sinceLast = System.currentTimeMillis() - lastClickAt;
+      if (sinceLast < DOUBLE_CLICK_WINDOW_MS) {
+        try {
+          Thread.sleep(DOUBLE_CLICK_WINDOW_MS - sinceLast);
+        } catch (InterruptedException e) {
+          Thread.currentThread().interrupt();
+          throw new IllegalStateException("Interrupted while waiting to click 
the canvas", e);
+        }
+      }
+    }
     new Actions(driver).moveToElement(canvas(), offsetX, 
offsetY).click().perform();
+    lastClickAt = System.currentTimeMillis();
+    lastClickX = offsetX;
+    lastClickY = offsetY;
   }
 
   /**

Reply via email to