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;
}
/**