From eea42fafaac76468c2d3a88ccf6768a3b794b1bc Mon Sep 17 00:00:00 2001 From: Marco Collovati Date: Sat, 28 Feb 2026 17:08:25 +0100 Subject: [PATCH] fix: use Selenium Actions for click() TestBenchElement.click() was using JavaScript's element.click(), which does not produce the same browser events as a real user click. This made click() behave differently from click(x, y), doubleClick(), and contextClick(), which all use Selenium Actions and correctly simulate user interactions. Side effects include missing or incorrect browser event properties such as click count. Replace the default click() implementation with Selenium Actions.click(), consistent with the other click methods. The old JavaScript-based click logic is extracted into a new javascriptClick() method for cases where Selenium cannot reach the element directly. A useJavascriptClick parameter is added to Parameters for backwards compatibility, allowing existing projects to restore the old behavior via system property or programmatic setter. Fixes #2155 --- .../com/vaadin/testbench/ParametersTest.java | 1 + .../com/vaadin/testUI/ClickCountView.java | 32 +++++++++++ .../vaadin/tests/elements/ClickCountIT.java | 55 +++++++++++++++++++ .../java/com/vaadin/testbench/Parameters.java | 34 ++++++++++++ .../vaadin/testbench/TestBenchElement.java | 33 +++++++++-- 5 files changed, 150 insertions(+), 5 deletions(-) create mode 100644 vaadin-testbench-integration-tests-junit5/src/main/java/com/vaadin/testUI/ClickCountView.java create mode 100644 vaadin-testbench-integration-tests-junit5/src/test/java/com/vaadin/tests/elements/ClickCountIT.java diff --git a/vaadin-testbench-core-junit5/src/test/java/com/vaadin/testbench/ParametersTest.java b/vaadin-testbench-core-junit5/src/test/java/com/vaadin/testbench/ParametersTest.java index 766bb8757..beb0960c6 100644 --- a/vaadin-testbench-core-junit5/src/test/java/com/vaadin/testbench/ParametersTest.java +++ b/vaadin-testbench-core-junit5/src/test/java/com/vaadin/testbench/ParametersTest.java @@ -30,6 +30,7 @@ public void testDefaultValues() { Assertions.assertEquals(false, Parameters.isScreenshotComparisonCursorDetection()); Assertions.assertFalse(Parameters.isHeadless()); + Assertions.assertFalse(Parameters.isUseJavascriptClick()); Assertions.assertEquals( ClientConfig.defaultConfig().readTimeout().toSeconds(), Parameters.getReadTimeout()); diff --git a/vaadin-testbench-integration-tests-junit5/src/main/java/com/vaadin/testUI/ClickCountView.java b/vaadin-testbench-integration-tests-junit5/src/main/java/com/vaadin/testUI/ClickCountView.java new file mode 100644 index 000000000..cbbddc251 --- /dev/null +++ b/vaadin-testbench-integration-tests-junit5/src/main/java/com/vaadin/testUI/ClickCountView.java @@ -0,0 +1,32 @@ +/** + * Copyright (C) 2000-2026 Vaadin Ltd + * + * This program is available under Vaadin Commercial License and Service Terms. + * + * See for the full + * license. + */ +package com.vaadin.testUI; + +import com.vaadin.flow.component.html.Div; +import com.vaadin.flow.component.html.NativeButton; +import com.vaadin.flow.router.Route; + +@Route("ClickCountView") +public class ClickCountView extends Div { + + public ClickCountView() { + NativeButton button = new NativeButton("Click me"); + button.setId("click-target"); + + Div log = new Div(); + log.setId("click-count"); + + button.getElement().addEventListener("click", e -> { + log.setText(String + .valueOf(e.getEventData().get("event.detail").intValue())); + }).addEventData("event.detail"); + + add(button, log); + } +} diff --git a/vaadin-testbench-integration-tests-junit5/src/test/java/com/vaadin/tests/elements/ClickCountIT.java b/vaadin-testbench-integration-tests-junit5/src/test/java/com/vaadin/tests/elements/ClickCountIT.java new file mode 100644 index 000000000..0dacd5364 --- /dev/null +++ b/vaadin-testbench-integration-tests-junit5/src/test/java/com/vaadin/tests/elements/ClickCountIT.java @@ -0,0 +1,55 @@ +/** + * Copyright (C) 2000-2026 Vaadin Ltd + * + * This program is available under Vaadin Commercial License and Service Terms. + * + * See for the full + * license. + */ +package com.vaadin.tests.elements; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeEach; + +import com.vaadin.flow.component.Component; +import com.vaadin.testUI.ClickCountView; +import com.vaadin.testbench.BrowserTest; +import com.vaadin.testbench.TestBenchElement; +import com.vaadin.tests.AbstractBrowserTB9Test; + +public class ClickCountIT extends AbstractBrowserTB9Test { + + @Override + protected Class getTestView() { + return ClickCountView.class; + } + + @BeforeEach + public void open() { + openTestURL(); + } + + @BrowserTest + public void click_hasClickCountOne() { + TestBenchElement button = $(NativeButtonElement.class) + .id("click-target"); + button.click(); + Assertions.assertEquals("1", $("div").id("click-count").getText()); + } + + @BrowserTest + public void clickWithCoordinates_hasClickCountOne() { + TestBenchElement button = $(NativeButtonElement.class) + .id("click-target"); + button.click(0, 0); + Assertions.assertEquals("1", $("div").id("click-count").getText()); + } + + @BrowserTest + public void doubleClick_hasClickCountTwo() { + TestBenchElement button = $(NativeButtonElement.class) + .id("click-target"); + button.doubleClick(); + Assertions.assertEquals("2", $("div").id("click-count").getText()); + } +} diff --git a/vaadin-testbench-shared/src/main/java/com/vaadin/testbench/Parameters.java b/vaadin-testbench-shared/src/main/java/com/vaadin/testbench/Parameters.java index 46c48e5f0..56ab2cf95 100644 --- a/vaadin-testbench-shared/src/main/java/com/vaadin/testbench/Parameters.java +++ b/vaadin-testbench-shared/src/main/java/com/vaadin/testbench/Parameters.java @@ -37,6 +37,7 @@ public class Parameters { private static int readTimeout; private static int hubPort; private static String[] chromeOptions; + private static boolean useJavascriptClick; static { isDebug = getSystemPropertyBoolean("debug", false); @@ -68,6 +69,8 @@ public class Parameters { (int) ClientConfig.defaultConfig().readTimeout().toSeconds()); hubPort = getSystemPropertyInt("hubPort", 4444); setChromeOptions(getSystemPropertyString("chromeOptions", null)); + useJavascriptClick = getSystemPropertyBoolean("useJavascriptClick", + false); } /** @@ -564,4 +567,35 @@ public static String[] getChromeOptions() { public static int getReadTimeout() { return readTimeout; } + + /** + * Returns whether {@link TestBenchElement#click()} should use JavaScript + * instead of Selenium Actions. + *

+ * JavaScript click avoids "element not clickable at point" issues but does + * not correctly simulate a real user interaction in the browser (e.g., + * browser event properties such as click count may be missing or + * incorrect). + * + * @return {@code true} if JavaScript click is used by default, + * {@code false} otherwise + */ + public static boolean isUseJavascriptClick() { + return useJavascriptClick; + } + + /** + * Sets whether {@link TestBenchElement#click()} should use JavaScript + * instead of Selenium Actions. + *

+ * Can also be set with the system property + * {@code com.vaadin.testbench.Parameters.useJavascriptClick=true}. + * + * @param useJavascriptClick + * {@code true} to use JavaScript click, {@code false} to use + * Selenium Actions + */ + public static void setUseJavascriptClick(boolean useJavascriptClick) { + Parameters.useJavascriptClick = useJavascriptClick; + } } diff --git a/vaadin-testbench-shared/src/main/java/com/vaadin/testbench/TestBenchElement.java b/vaadin-testbench-shared/src/main/java/com/vaadin/testbench/TestBenchElement.java index 1d528dce1..c22fc5851 100644 --- a/vaadin-testbench-shared/src/main/java/com/vaadin/testbench/TestBenchElement.java +++ b/vaadin-testbench-shared/src/main/java/com/vaadin/testbench/TestBenchElement.java @@ -191,16 +191,39 @@ public boolean isFocused() { @Override public void click() { - // JS call to click does not focus element, hence ensure focus + if (Parameters.isUseJavascriptClick()) { + javascriptClick(); + } else { + autoScrollIntoView(); + waitForVaadin(); + new Actions(getDriver()).click(wrappedElement).build().perform(); + } + } + + /** + * Clicks the element using JavaScript rather than Selenium Actions. + *

+ * This avoids "element not clickable at point" issues that can occur when + * the element is behind an overlay or otherwise not directly reachable by + * Selenium, but it does not correctly simulate a real user interaction in + * the browser (e.g., browser event properties such as click count may be + * missing or incorrect). + *

+ * Prefer {@link #click()} for normal use. Use this method only when you + * need to click an element that Selenium cannot reach directly. + *

+ * To use this method globally as the default for {@link #click()}, set the + * system property + * {@code com.vaadin.testbench.Parameters.useJavascriptClick=true} or call + * {@link Parameters#setUseJavascriptClick(boolean)}. + */ + public void javascriptClick() { focus(); try { - // Avoid strange "element not clickable at point" problems callFunction("click"); } catch (Exception e) { - if (e.getMessage() + if (e.getMessage() != null && e.getMessage() .contains("Inspected target navigated or closed")) { - // This happens with chromedriver although e.g. navigation - // succeeds return; } // SVG elements and maybe others do not have a 'click' method