Skip to content

Commit

Permalink
#2288 Wait until element becomes enabled before click (#2290)
Browse files Browse the repository at this point in the history
* #2288 Wait until element becomes enabled before click

Before the change the clicked element had to be `interactable`, but after the change it must be `clickable: interactable and enabled`
  • Loading branch information
Au6ojlut committed May 23, 2023
1 parent a970b6a commit 743a3d1
Show file tree
Hide file tree
Showing 7 changed files with 84 additions and 6 deletions.
23 changes: 22 additions & 1 deletion src/main/java/com/codeborne/selenide/SelenideElement.java
Original file line number Diff line number Diff line change
Expand Up @@ -1249,13 +1249,21 @@ public interface SelenideElement extends WebElement, WrapsDriver, WrapsElement,
SelenideElement cached();

/**
* Click the element using {@link ClickOptions}: {@code $("#username").click(ClickOptions.usingJavaScript())}<p>
* Click the element using {@link ClickOptions}:
*
* <pre>
* {@code $("#username").click(ClickOptions.usingJavaScript())}
* </pre>
*
* You can specify a relative offset from the center of the element inside ClickOptions:
* e.g. <pre>
* {@code $("#username").click(usingJavaScript().offset(123, 222))}
* </pre>
*
* Before clicking, waits until element gets interactable and enabled.
* <br>
*
* @return this element
* @see com.codeborne.selenide.commands.Click
*/
SelenideElement click(ClickOptions clickOption);
Expand All @@ -1268,6 +1276,10 @@ public interface SelenideElement extends WebElement, WrapsDriver, WrapsElement,
* But it uses JavaScript method to click if {@code com.codeborne.selenide.Configuration#clickViaJs} is defined.
* It may be helpful for testing in Internet Explorer where native click doesn't always work correctly.
*
* <br><br>
* Before clicking, waits until element gets interactable and enabled.
* <br>
*
* @see com.codeborne.selenide.commands.Click
*/
@Override
Expand All @@ -1286,6 +1298,10 @@ public interface SelenideElement extends WebElement, WrapsDriver, WrapsElement,
/**
* Double-click the element
*
* <br><br>
* Before clicking, waits until element gets interactable and enabled.
* <br>
*
* @return this element
* @see com.codeborne.selenide.commands.DoubleClick
*/
Expand All @@ -1301,6 +1317,11 @@ public interface SelenideElement extends WebElement, WrapsDriver, WrapsElement,
* e.g. {@code $("#username").doubleClick(usingJavaScript().offset(123, 222))}
* </p>
*
* <br>
* Before clicking, waits until element gets interactable and enabled.
* <br>
*
* @return this element
* @see com.codeborne.selenide.commands.DoubleClick
* @since 6.13.0
*/
Expand Down
2 changes: 1 addition & 1 deletion src/main/java/com/codeborne/selenide/commands/Click.java
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ else if (args.length == 1) {
@Nonnull
@CheckReturnValue
protected WebElement findElement(WebElementSource locator) {
return locator.findAndAssertElementIsInteractable();
return locator.findAndAssertElementIsClickable();
}

protected void click(Driver driver, WebElement element) {
Expand Down
15 changes: 15 additions & 0 deletions src/main/java/com/codeborne/selenide/impl/WebElementSource.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import com.codeborne.selenide.Condition;
import com.codeborne.selenide.Driver;
import com.codeborne.selenide.SelenideElement;
import com.codeborne.selenide.conditions.And;
import com.codeborne.selenide.ex.ElementNotFound;
import com.codeborne.selenide.ex.ElementShould;
import com.codeborne.selenide.ex.ElementShouldNot;
Expand All @@ -21,9 +22,11 @@

import static com.codeborne.selenide.CheckResult.Verdict.ACCEPT;
import static com.codeborne.selenide.Condition.editable;
import static com.codeborne.selenide.Condition.enabled;
import static com.codeborne.selenide.Condition.interactable;
import static com.codeborne.selenide.Condition.not;
import static com.codeborne.selenide.impl.Alias.NONE;
import static java.util.Arrays.asList;
import static java.util.Collections.singletonList;
import static java.util.Objects.requireNonNull;

Expand Down Expand Up @@ -158,6 +161,18 @@ public WebElement findAndAssertElementIsInteractable() {
return requireNonNull(checkConditionAndReturnElement("be ", interactable, false));
}

/**
* Asserts that returned element is enabled and can be interacted with.
*
* @return element or throws ElementShould/ElementShouldNot exceptions
* @since 6.15.0
*/
@Nonnull
@CheckReturnValue
public WebElement findAndAssertElementIsClickable() {
return requireNonNull(checkConditionAndReturnElement("be ", new And("clickable", asList(interactable, enabled)), false));
}

/**
* Asserts that returned element is editable.
* @return element or throws ElementShould/ElementShouldNot exceptions
Expand Down
16 changes: 16 additions & 0 deletions src/test/resources/page_with_disabled_button_click.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<!DOCTYPE html>
<html lang="en">
<head>
<title>Position click test</title>
<meta charset="UTF-8">
</head>
<body>
<div id="page">THIS IS A TEST</div>
<form>
<label> Name:
<input value="name" type="text"/>
</label>
<button id="submit" type="submit" disabled="disabled">SUBMIT</button>
</form>
</body>
</html>
26 changes: 26 additions & 0 deletions statics/src/test/java/integration/ClickDisabledButtonTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package integration;

import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;

import static com.codeborne.selenide.Selenide.$;
import static org.assertj.core.api.Assertions.assertThatThrownBy;

final class ClickDisabledButtonTest extends IntegrationTest {
@BeforeEach
void openTestPage() {
openFile("page_with_disabled_button_click.html");
}

@Test
void failToClickIfButtonIsDisabled() {
assertThatThrownBy(() -> $("#submit").click())
.hasMessageStartingWith("Element should be clickable: interactable and enabled {#submit}");
}

@Test
void failToDoubleClickIfButtonIsDisabled() {
assertThatThrownBy(() -> $("#submit").doubleClick())
.hasMessageStartingWith("Element should be clickable: interactable and enabled {#submit}");
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ void actionWithVisibilityWaiting_WhenCollectionElementByIndex_WithIndexOutOfRang
assertThatThrownBy(() -> element.click())
.isInstanceOf(ElementNotFound.class)
.hasMessageStartingWith("Element not found {ul li[10]}")
.hasMessageContaining("Expected: interactable")
.hasMessageContaining("Expected: clickable: interactable and enabled")
.has(screenshot())
.getCause()
.isInstanceOf(IndexOutOfBoundsException.class)
Expand Down Expand Up @@ -165,7 +165,7 @@ void actionWithVisibilityWaiting_WhenInnerElement_WithNonExistentInnerElement()
assertThatThrownBy(() -> element.doubleClick())
.isInstanceOf(ElementNotFound.class)
.hasMessageStartingWith("Element not found {ul/.nonexistent}")
.hasMessageContaining("Expected: interactable")
.hasMessageContaining("Expected: clickable: interactable and enabled")
.has(screenshot())
.getCause()
.isInstanceOf(NoSuchElementException.class)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,7 @@ void clickHiddenElement() {
)
.isInstanceOf(ElementShould.class)
.hasMessageMatching(String.format(
"Element should be interactable \\{#theHiddenElement}%n" +
"Element should be clickable: interactable and enabled \\{#theHiddenElement}%n" +
"Element: '<div id=\"theHiddenElement\" displayed:false></div>'%n" +
"Actual value: hidden, opacity=1%n" +
"Screenshot: " + path + png() + "%n" +
Expand Down Expand Up @@ -179,7 +179,7 @@ void clickUnexistingWrappedElement() {
$(pageObject.categoryDropdown).click()
).isInstanceOf(ElementNotFound.class)
.hasMessageMatching(String.format("Element not found \\{By.id: invalid_id}%n" +
"Expected: interactable%n" +
"Expected: clickable: interactable and enabled%n" +
"Screenshot: %s%s%n" +
"Page source: %s%s%n" +
"Timeout: 15 ms.%n" +
Expand Down

0 comments on commit 743a3d1

Please sign in to comment.