Skip to content

Commit

Permalink
#2635 Add ability to perform click at disabled elements
Browse files Browse the repository at this point in the history
  • Loading branch information
asolntsev committed Feb 2, 2024
1 parent 88a88ee commit 60d7e69
Show file tree
Hide file tree
Showing 8 changed files with 89 additions and 26 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
* #2550 Implement downloading files via CDP or BiDi (#2567) - thanks to Sergey Brit!
* #2612 don't set page load timeout in mobile tests (#2628)
* #2617 User can safely add the same proxy filter many times (#2630)
* #2635 Add ability to perform click at disabled elements (#2636)
* #2624 fix duplicate authentication (#2626)
* mask BasicAuth password in logs (#2626)
* #2609 detect case when an event collect is reused by different threads
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ public class AppiumClickOptions extends ClickOptions {
private static final Duration DEFAULT_LONG_PRESS_DURATION = ofSeconds(3);

private AppiumClickOptions(AppiumClickMethod appiumClickMethod, int offsetX, int offsetY, Duration longPressHoldDuration) {
super(ClickMethod.DEFAULT, offsetX, offsetY, null);
super(ClickMethod.DEFAULT, offsetX, offsetY, null, true);
this.appiumClickMethod = appiumClickMethod;
this.longPressHoldDuration = longPressHoldDuration;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ public SelenideElement execute(SelenideElement proxy, WebElementSource locator,
return super.execute(proxy, locator, args);
}

WebElement webElement = findElement(locator);
WebElement webElement = findElement(locator, true);

if (args == null || args.length == 0) {
click(locator.driver(), webElement);
Expand Down Expand Up @@ -139,11 +139,11 @@ public Point getCenter(WebElement element) {
@Override
@Nonnull
@CheckReturnValue
protected WebElement findElement(WebElementSource locator) {
protected WebElement findElement(WebElementSource locator, boolean disabledElementAllowed) {
if (isMobile(locator.driver())) {
return locator.getWebElement();
} else {
return super.findElement(locator);
return super.findElement(locator, disabledElementAllowed);
}
}
}
36 changes: 27 additions & 9 deletions src/main/java/com/codeborne/selenide/ClickOptions.java
Original file line number Diff line number Diff line change
Expand Up @@ -18,36 +18,40 @@ public class ClickOptions implements HasTimeout {
private final ClickMethod clickMethod;
@Nullable
private final Duration timeout;
private final boolean disabledElementAllowed;

protected ClickOptions(ClickMethod clickMethod, int offsetX, int offsetY, @Nullable Duration timeout) {
protected ClickOptions(ClickMethod clickMethod, int offsetX, int offsetY,
@Nullable Duration timeout,
boolean disabledElementAllowed) {
this.clickMethod = clickMethod;
this.offsetX = offsetX;
this.offsetY = offsetY;
this.timeout = timeout;
this.disabledElementAllowed = disabledElementAllowed;
}

@CheckReturnValue
@Nonnull
public static ClickOptions usingDefaultMethod() {
return new ClickOptions(DEFAULT, 0, 0, null);
return new ClickOptions(DEFAULT, 0, 0, null, false);
}

@CheckReturnValue
@Nonnull
public static ClickOptions usingJavaScript() {
return new ClickOptions(JS, 0, 0, null);
return new ClickOptions(JS, 0, 0, null, false);
}

@CheckReturnValue
@Nonnull
public static ClickOptions withOffset(int offsetX, int offsetY) {
return new ClickOptions(DEFAULT, offsetX, offsetY, null);
return new ClickOptions(DEFAULT, offsetX, offsetY, null, false);
}

@CheckReturnValue
@Nonnull
public static ClickOptions withTimeout(Duration timeout) {
return new ClickOptions(DEFAULT, 0, 0, timeout);
return new ClickOptions(DEFAULT, 0, 0, timeout, false);
}

@CheckReturnValue
Expand All @@ -73,22 +77,27 @@ public Duration timeout() {
return timeout;
}

@CheckReturnValue
public boolean isDisabledElementAllowed() {
return disabledElementAllowed;
}

@CheckReturnValue
@Nonnull
public ClickOptions offsetX(int offsetX) {
return new ClickOptions(clickMethod, offsetX, offsetY, timeout);
return new ClickOptions(clickMethod, offsetX, offsetY, timeout, disabledElementAllowed);
}

@CheckReturnValue
@Nonnull
public ClickOptions offsetY(int offsetY) {
return new ClickOptions(clickMethod, offsetX, offsetY, timeout);
return new ClickOptions(clickMethod, offsetX, offsetY, timeout, disabledElementAllowed);
}

@CheckReturnValue
@Nonnull
public ClickOptions offset(int offsetX, int offsetY) {
return new ClickOptions(clickMethod, offsetX, offsetY, timeout);
return new ClickOptions(clickMethod, offsetX, offsetY, timeout, disabledElementAllowed);
}

/**
Expand All @@ -109,7 +118,16 @@ public ClickOptions offset(int offsetX, int offsetY) {
@CheckReturnValue
@Nonnull
public ClickOptions timeout(Duration timeout) {
return new ClickOptions(clickMethod, offsetX, offsetY, timeout);
return new ClickOptions(clickMethod, offsetX, offsetY, timeout, disabledElementAllowed);
}

/**
* @since 7.1.0
*/
@CheckReturnValue
@Nonnull
public ClickOptions allowDisabled() {
return new ClickOptions(clickMethod, offsetX, offsetY, timeout, true);
}

@Override
Expand Down
25 changes: 12 additions & 13 deletions src/main/java/com/codeborne/selenide/commands/Click.java
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,9 @@
import java.time.Duration;
import java.util.Arrays;

import static com.codeborne.selenide.ClickOptions.usingDefaultMethod;
import static com.codeborne.selenide.commands.Util.firstOf;
import static com.codeborne.selenide.commands.Util.size;

@ParametersAreNonnullByDefault
public class Click implements Command<SelenideElement> {
Expand All @@ -25,25 +27,22 @@ public class Click implements Command<SelenideElement> {
@Override
@Nonnull
public SelenideElement execute(SelenideElement proxy, WebElementSource locator, @Nullable Object[] args) {
WebElement webElement = findElement(locator);
ClickOptions clickOptions = switch (size(args)) {
case 0 -> usingDefaultMethod();
case 1 -> firstOf(args);
default -> throw new IllegalArgumentException("Unsupported click arguments: " + Arrays.toString(args));
};

if (args == null || args.length == 0) {
click(locator.driver(), webElement);
}
else if (args.length == 1) {
ClickOptions clickOptions = firstOf(args);
click(locator.driver(), webElement, clickOptions);
}
else {
throw new IllegalArgumentException("Unsupported click arguments: " + Arrays.toString(args));
}
click(locator.driver(), findElement(locator, clickOptions.isDisabledElementAllowed()), clickOptions);
return proxy;
}

@Nonnull
@CheckReturnValue
protected WebElement findElement(WebElementSource locator) {
return locator.findAndAssertElementIsClickable();
protected WebElement findElement(WebElementSource locator, boolean disabledElementAllowed) {
return disabledElementAllowed ?
locator.findAndAssertElementIsInteractable() :
locator.findAndAssertElementIsClickable();
}

protected void click(Driver driver, WebElement element) {
Expand Down
5 changes: 5 additions & 0 deletions src/main/java/com/codeborne/selenide/commands/Util.java
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,11 @@

@ParametersAreNonnullByDefault
public class Util {
@CheckReturnValue
public static int size(@Nullable Object[] args) {
return args == null ? 0 : args.length;
}

@SuppressWarnings("unchecked")
@CheckReturnValue
@Nonnull
Expand Down
24 changes: 24 additions & 0 deletions src/test/java/com/codeborne/selenide/commands/UtilCommandTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,35 @@
import static com.codeborne.selenide.Condition.readonly;
import static com.codeborne.selenide.Condition.visible;
import static com.codeborne.selenide.commands.Util.argsToConditions;
import static com.codeborne.selenide.commands.Util.firstOf;
import static com.codeborne.selenide.commands.Util.size;
import static java.util.Arrays.asList;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;

final class UtilCommandTest {
@Test
void sizeOfArray() {
assertThat(size(null)).isEqualTo(0);
assertThat(size(new String[]{})).isEqualTo(0);
assertThat(size(new String[]{"one"})).isEqualTo(1);
assertThat(size(new String[]{"one", "two"})).isEqualTo(2);
}

@Test
void firstElementOfArray() {
assertThatThrownBy(() -> firstOf(null))
.isInstanceOf(IllegalArgumentException.class)
.hasMessage("Missing arguments");

assertThatThrownBy(() -> firstOf(new String[]{}))
.isInstanceOf(IllegalArgumentException.class)
.hasMessage("Missing arguments");

String firstElement = firstOf(new String[]{"one", "two"});
assertThat(firstElement).isEqualTo("one");
}

@Test
void extractsConditionsFromGivenArguments() {
List<WebElementCondition> conditions = argsToConditions(new Object[]{enabled, visible});
Expand Down
16 changes: 16 additions & 0 deletions statics/src/test/java/integration/ClickDisabledButtonTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;

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

final class ClickDisabledButtonTest extends IntegrationTest {
Expand All @@ -18,9 +20,23 @@ void failToClickIfButtonIsDisabled() {
.hasMessageStartingWith("Element should be clickable: interactable and enabled {#submit}");
}

@Test
void clickingDisabledButtonMayBeAllowed() {
assertThatNoException().isThrownBy(() ->
$("#submit").click(usingDefaultMethod().allowDisabled())
);
}

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

@Test
void doubleClickingDisabledButtonMayBeAllowed() {
assertThatNoException().isThrownBy(() ->
$("#submit").doubleClick(usingDefaultMethod().allowDisabled())
);
}
}

0 comments on commit 60d7e69

Please sign in to comment.