Skip to content

Commit 7ec04dc

Browse files
vaadin-botTatuLund
andauthored
fix: Fix click count in TestBenchElement#click() (#2120) (#2125)
Co-authored-by: Tatu Lund <tatu@vaadin.com>
1 parent 22302a7 commit 7ec04dc

3 files changed

Lines changed: 172 additions & 22 deletions

File tree

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
/**
2+
* Copyright (C) 2000-2026 Vaadin Ltd
3+
*
4+
* This program is available under Vaadin Commercial License and Service Terms.
5+
*
6+
* See <https://vaadin.com/commercial-license-and-service-terms> for the full
7+
* license.
8+
*/
9+
package com.vaadin.testUI;
10+
11+
import com.vaadin.flow.component.html.Div;
12+
import com.vaadin.flow.component.html.NativeButton;
13+
import com.vaadin.flow.component.html.Span;
14+
import com.vaadin.flow.router.Route;
15+
16+
@Route("ButtonView")
17+
public class ButtonView extends Div {
18+
19+
public ButtonView() {
20+
NativeButton button = new NativeButton("Click me");
21+
button.setId("test-button");
22+
button.addSingleClickListener(event -> {
23+
Span newSpan = new Span(
24+
"Button single clicked: " + event.getClickCount());
25+
newSpan.setId("single-click");
26+
add(newSpan);
27+
});
28+
button.addDoubleClickListener(event -> {
29+
Span newSpan = new Span(
30+
"Button double clicked: " + event.getClickCount());
31+
newSpan.setId("double-click");
32+
add(newSpan);
33+
});
34+
button.addFocusListener(event -> {
35+
Span newSpan = new Span("Button focused");
36+
newSpan.setId("focus-event");
37+
add(newSpan);
38+
});
39+
add(button);
40+
}
41+
42+
}
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
/**
2+
* Copyright (C) 2000-2026 Vaadin Ltd
3+
*
4+
* This program is available under Vaadin Commercial License and Service Terms.
5+
*
6+
* See <https://vaadin.com/commercial-license-and-service-terms> for the full
7+
* license.
8+
*/
9+
package com.vaadin.tests.elements;
10+
11+
import org.junit.Before;
12+
import org.junit.Test;
13+
import org.openqa.selenium.By;
14+
15+
import com.vaadin.flow.component.Component;
16+
import com.vaadin.testUI.ButtonView;
17+
import com.vaadin.tests.AbstractTB6Test;
18+
19+
import static org.junit.Assert.assertEquals;
20+
21+
public class ButtonIT extends AbstractTB6Test {
22+
23+
@Override
24+
protected Class<? extends Component> getTestView() {
25+
return ButtonView.class;
26+
}
27+
28+
@Before
29+
public void openAndFindElement() {
30+
openTestURL();
31+
}
32+
33+
@Test
34+
public void buttonClick_singleClick() {
35+
$(NativeButtonElement.class).id("test-button").click();
36+
assertEquals("Button single clicked: 1",
37+
findElement(By.id("single-click")).getText());
38+
assertEquals("Button focused",
39+
findElement(By.id("focus-event")).getText());
40+
}
41+
42+
@Test
43+
public void buttonClick_doubleClick() {
44+
$(NativeButtonElement.class).id("test-button").doubleClick();
45+
assertEquals("Button double clicked: 2",
46+
findElement(By.id("double-click")).getText());
47+
assertEquals("Button focused",
48+
findElement(By.id("focus-event")).getText());
49+
}
50+
51+
}

vaadin-testbench-shared/src/main/java/com/vaadin/testbench/TestBenchElement.java

Lines changed: 79 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -191,23 +191,9 @@ public boolean isFocused() {
191191

192192
@Override
193193
public void click() {
194-
// JS call to click does not focus element, hence ensure focus
195-
focus();
196-
try {
197-
// Avoid strange "element not clickable at point" problems
198-
callFunction("click");
199-
} catch (Exception e) {
200-
if (e.getMessage()
201-
.contains("Inspected target navigated or closed")) {
202-
// This happens with chromedriver although e.g. navigation
203-
// succeeds
204-
return;
205-
}
206-
// SVG elements and maybe others do not have a 'click' method
207-
autoScrollIntoView();
208-
waitForVaadin();
209-
wrappedElement.click();
210-
}
194+
autoScrollIntoView();
195+
waitForVaadin();
196+
new Actions(getDriver()).click(wrappedElement).build().perform();
211197
}
212198

213199
@Override
@@ -363,12 +349,20 @@ public void click(int x, int y, Keys... modifiers) {
363349
actions.build().perform();
364350
}
365351

352+
/**
353+
* Performs a double-click action on this element.
354+
*
355+
* @see click()
356+
*/
366357
public void doubleClick() {
367358
autoScrollIntoView();
368359
waitForVaadin();
369360
new Actions(getDriver()).doubleClick(wrappedElement).build().perform();
370361
}
371362

363+
/**
364+
* Performs a context-click (right click) action on this element.
365+
*/
372366
public void contextClick() {
373367
autoScrollIntoView();
374368
waitForVaadin();
@@ -520,7 +514,20 @@ public boolean compareScreen(BufferedImage reference, String referenceName)
520514
(TakesScreenshot) this, (HasCapabilities) getDriver());
521515
}
522516

523-
/***
517+
/**
518+
* Scrolls the element into the visible area of the browser window with the
519+
* given options. Check
520+
* https://developer.mozilla.org/en-US/docs/Web/API/Element/scrollIntoView
521+
* for more information on the options.
522+
*
523+
* @param options
524+
* the parameters for scrolling into view
525+
*/
526+
public void scrollIntoView(Map<String, Object> options) {
527+
callFunction("scrollIntoView", options);
528+
}
529+
530+
/**
524531
* Scrolls the element into the visible area of the browser window
525532
*/
526533
public void scrollIntoView() {
@@ -534,15 +541,65 @@ public void scrollIntoView() {
534541
*/
535542
private void autoScrollIntoView() {
536543
try {
537-
if (getCommandExecutor().isAutoScrollIntoView()) {
538-
if (!wrappedElement.isDisplayed()) {
539-
scrollIntoView();
540-
}
544+
if (getCommandExecutor().isAutoScrollIntoView()
545+
&& !isElementInViewport()) {
546+
var params = Map.<String, Object> of("block", "end", "inline",
547+
"end");
548+
scrollIntoView(params);
541549
}
542550
} catch (Exception e) {
543551
}
544552
}
545553

554+
private boolean isElementInViewport() {
555+
try {
556+
Boolean result = (Boolean) executeScript(
557+
"""
558+
function isVisible(elem) {
559+
// Check if element has zero dimensions (truly hidden)
560+
if (!elem.offsetParent && elem.offsetWidth === 0 && elem.offsetHeight === 0) {
561+
return false;
562+
}
563+
564+
var rect = elem.getBoundingClientRect();
565+
if (rect.width === 0 || rect.height === 0) {
566+
return false;
567+
}
568+
569+
// Check if element intersects with viewport
570+
var windowHeight = window.innerHeight || document.documentElement.clientHeight;
571+
var windowWidth = window.innerWidth || document.documentElement.clientWidth;
572+
if (rect.bottom < 0 || rect.top > windowHeight || rect.right < 0 || rect.left > windowWidth) {
573+
return false;
574+
}
575+
576+
// Check if clipped by any scrollable ancestor
577+
var parent = elem.parentElement;
578+
while (parent && parent !== document.body) {
579+
var style = window.getComputedStyle(parent);
580+
var overflow = style.overflow + style.overflowX + style.overflowY;
581+
582+
if (overflow.includes('hidden') || overflow.includes('scroll') || overflow.includes('auto')) {
583+
var parentRect = parent.getBoundingClientRect();
584+
// Check if element is outside parent's visible area
585+
if (rect.bottom < parentRect.top || rect.top > parentRect.bottom ||
586+
rect.right < parentRect.left || rect.left > parentRect.right) {
587+
return false;
588+
}
589+
}
590+
parent = parent.parentElement;
591+
}
592+
return true;
593+
}
594+
return isVisible(arguments[0]);
595+
""",
596+
this);
597+
return result != null && result;
598+
} catch (Exception e) {
599+
return true; // Assume visible on error
600+
}
601+
}
602+
546603
/**
547604
* Waits the given number of seconds for the given condition to become
548605
* neither null nor false. {@link NotFoundException}s are ignored by

0 commit comments

Comments
 (0)