Merge pull request #13850 from AndiCover/BAEL-5739_2

BAEL-5739 Avoiding the StaleElementReferenceException in Selenium
This commit is contained in:
davidmartinezbarua 2023-04-25 17:24:15 -03:00 committed by GitHub
commit bf3b038daa
5 changed files with 452 additions and 0 deletions

View File

@ -0,0 +1,86 @@
package com.baeldung.selenium.stale;
import org.openqa.selenium.By;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
public class RobustWebDriver implements WebDriver {
private final WebDriver originalWebDriver;
public RobustWebDriver(WebDriver webDriver) {
this.originalWebDriver = webDriver;
}
@Override
public void get(String url) {
this.originalWebDriver.get(url);
}
@Override
public String getCurrentUrl() {
return this.originalWebDriver.getCurrentUrl();
}
@Override
public String getTitle() {
return this.originalWebDriver.getTitle();
}
@Override
public List<WebElement> findElements(By by) {
return this.originalWebDriver.findElements(by)
.stream().map(e -> new RobustWebElement(e, by, this))
.collect(Collectors.toList());
}
@Override
public WebElement findElement(By by) {
return new RobustWebElement(this.originalWebDriver.findElement(by), by, this);
}
@Override
public String getPageSource() {
return this.originalWebDriver.getPageSource();
}
@Override
public void close() {
this.originalWebDriver.close();
}
@Override
public void quit() {
this.originalWebDriver.quit();
}
@Override
public Set<String> getWindowHandles() {
return this.originalWebDriver.getWindowHandles();
}
@Override
public String getWindowHandle() {
return this.originalWebDriver.getWindowHandle();
}
@Override
public TargetLocator switchTo() {
return this.originalWebDriver.switchTo();
}
@Override
public Navigation navigate() {
return this.originalWebDriver.navigate();
}
@Override
public Options manage() {
return this.originalWebDriver.manage();
}
}

View File

@ -0,0 +1,176 @@
package com.baeldung.selenium.stale;
import org.openqa.selenium.By;
import org.openqa.selenium.Dimension;
import org.openqa.selenium.OutputType;
import org.openqa.selenium.Point;
import org.openqa.selenium.Rectangle;
import org.openqa.selenium.StaleElementReferenceException;
import org.openqa.selenium.WebDriverException;
import org.openqa.selenium.WebElement;
import java.util.List;
import java.util.function.BiConsumer;
import java.util.function.BiFunction;
import java.util.function.Consumer;
import java.util.function.Function;
public class RobustWebElement implements WebElement {
private WebElement originalElement;
private final RobustWebDriver driver;
private final By by;
private static final int MAX_RETRIES = 10;
private static final String SERE = "Element is no longer attached to the DOM";
public RobustWebElement(WebElement element, By by, RobustWebDriver driver) {
this.originalElement = element;
this.by = by;
this.driver = driver;
}
@Override
public void click() {
executeMethodWithRetries(WebElement::click);
}
@Override
public void submit() {
executeMethodWithRetries(WebElement::submit);
}
@Override
public void sendKeys(CharSequence... keysToSend) {
executeMethodWithRetriesVoid(WebElement::sendKeys, keysToSend);
}
@Override
public void clear() {
executeMethodWithRetries(WebElement::clear);
}
@Override
public String getTagName() {
return executeMethodWithRetries(WebElement::getTagName);
}
@Override
public String getAttribute(String name) {
return executeMethodWithRetries(WebElement::getAttribute, name);
}
@Override
public boolean isSelected() {
return executeMethodWithRetries(WebElement::isSelected);
}
@Override
public boolean isEnabled() {
return executeMethodWithRetries(WebElement::isEnabled);
}
@Override
public String getText() {
return executeMethodWithRetries(WebElement::getText);
}
@Override
public List<WebElement> findElements(By by) {
return executeMethodWithRetries(WebElement::findElements, by);
}
@Override
public WebElement findElement(By by) {
return executeMethodWithRetries(WebElement::findElement, by);
}
@Override
public boolean isDisplayed() {
return executeMethodWithRetries(WebElement::isDisplayed);
}
@Override
public Point getLocation() {
return executeMethodWithRetries(WebElement::getLocation);
}
@Override
public Dimension getSize() {
return executeMethodWithRetries(WebElement::getSize);
}
@Override
public Rectangle getRect() {
return executeMethodWithRetries(WebElement::getRect);
}
@Override
public String getCssValue(String propertyName) {
return executeMethodWithRetries(WebElement::getCssValue, propertyName);
}
@Override
public <X> X getScreenshotAs(OutputType<X> target) throws WebDriverException {
return executeMethodWithRetries(WebElement::getScreenshotAs, target);
}
private void executeMethodWithRetries(Consumer<WebElement> method) {
int retries = 0;
while (retries < MAX_RETRIES) {
try {
WebElementUtils.callMethod(originalElement, method);
return;
} catch (StaleElementReferenceException ex) {
refreshElement();
}
retries++;
}
throw new StaleElementReferenceException(SERE);
}
private <T> T executeMethodWithRetries(Function<WebElement, T> method) {
int retries = 0;
while (retries < MAX_RETRIES) {
try {
return WebElementUtils.callMethodWithReturn(originalElement, method);
} catch (StaleElementReferenceException ex) {
refreshElement();
}
retries++;
}
throw new StaleElementReferenceException(SERE);
}
private <U> void executeMethodWithRetriesVoid(BiConsumer<WebElement, U> method, U parameter) {
int retries = 0;
while (retries < MAX_RETRIES) {
try {
WebElementUtils.callMethod(originalElement, method, parameter);
return;
} catch (StaleElementReferenceException ex) {
refreshElement();
}
retries++;
}
throw new StaleElementReferenceException(SERE);
}
private <T, U> T executeMethodWithRetries(BiFunction<WebElement, U, T> method, U parameter) {
int retries = 0;
while (retries < MAX_RETRIES) {
try {
return WebElementUtils.callMethodWithReturn(originalElement, method, parameter);
} catch (StaleElementReferenceException ex) {
refreshElement();
}
retries++;
}
throw new StaleElementReferenceException(SERE);
}
private void refreshElement() {
this.originalElement = driver.findElement(by);
}
}

View File

@ -0,0 +1,30 @@
package com.baeldung.selenium.stale;
import org.openqa.selenium.WebElement;
import java.util.function.BiConsumer;
import java.util.function.BiFunction;
import java.util.function.Consumer;
import java.util.function.Function;
public class WebElementUtils {
private WebElementUtils(){
}
public static void callMethod(WebElement element, Consumer<WebElement> method) {
method.accept(element);
}
public static <U> void callMethod(WebElement element, BiConsumer<WebElement, U> method, U parameter) {
method.accept(element, parameter);
}
public static <T> T callMethodWithReturn(WebElement element, Function<WebElement, T> method) {
return method.apply(element);
}
public static <T, U> T callMethodWithReturn(WebElement element, BiFunction<WebElement, U, T> method, U parameter) {
return method.apply(element, parameter);
}
}

View File

@ -0,0 +1,58 @@
package com.baeldung.selenium.stale;
import io.github.bonigarcia.wdm.WebDriverManager;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.openqa.selenium.By;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.chrome.ChromeDriver;
import org.openqa.selenium.chrome.ChromeOptions;
import java.time.Duration;
final class RobustWebElementLiveTest {
private static RobustWebDriver driver;
private static final int TIMEOUT = 10;
private static final By LOCATOR_REFRESH = By.xpath("//a[.='click here']");
private static final By LOCATOR_DYNAMIC_CONTENT = By.xpath(
"(//div[@id='content']//div[@class='large-10 columns'])[1]");
private static void setupChromeDriver() {
WebDriverManager.chromedriver().setup();
final ChromeOptions options = new ChromeOptions();
options.addArguments("--remote-allow-origins=*");
driver = new RobustWebDriver(new ChromeDriver(options));
options();
}
private static void options() {
driver.manage().window().maximize();
driver.manage().timeouts().implicitlyWait(Duration.ofSeconds(TIMEOUT));
}
@BeforeEach
public void init() {
setupChromeDriver();
}
@Test
void givenDynamicPage_whenRefreshingAndAccessingSavedElement_thenOK() {
driver.navigate().to("https://the-internet.herokuapp.com/dynamic_content?with_content=static");
final WebElement element = driver.findElement(LOCATOR_DYNAMIC_CONTENT);
driver.findElement(LOCATOR_REFRESH).click();
Assertions.assertDoesNotThrow(element::getText);
}
@AfterEach
void teardown() {
if (driver != null) {
driver.quit();
driver = null;
}
}
}

View File

@ -0,0 +1,102 @@
package com.baeldung.selenium.stale;
import io.github.bonigarcia.wdm.WebDriverManager;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.openqa.selenium.By;
import org.openqa.selenium.StaleElementReferenceException;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.chrome.ChromeDriver;
import org.openqa.selenium.chrome.ChromeOptions;
import java.time.Duration;
final class StaleElementReferenceLiveTest {
private static WebDriver driver;
private static final int TIMEOUT = 10;
private static final By LOCATOR_REFRESH = By.xpath("//a[.='click here']");
private static final By LOCATOR_DYNAMIC_CONTENT = By.xpath(
"(//div[@id='content']//div[@class='large-10 columns'])[1]");
private static void setupChromeDriver() {
WebDriverManager.chromedriver().setup();
final ChromeOptions options = new ChromeOptions();
options.addArguments("--remote-allow-origins=*");
driver = new ChromeDriver(options);
options();
}
private static void options() {
driver.manage().window().maximize();
driver.manage().timeouts().implicitlyWait(Duration.ofSeconds(TIMEOUT));
}
@BeforeEach
public void init() {
setupChromeDriver();
}
@Test
void givenDynamicPage_whenRefreshingAndAccessingSavedElement_thenSERE() {
driver.navigate().to("https://the-internet.herokuapp.com/dynamic_content?with_content=static");
final WebElement element = driver.findElement(LOCATOR_DYNAMIC_CONTENT);
driver.findElement(LOCATOR_REFRESH).click();
Assertions.assertThrows(StaleElementReferenceException.class, element::getText);
}
@Test
void givenDynamicPage_whenRefreshingAndAccessingSavedElement_thenHandleSERE() {
driver.navigate().to("https://the-internet.herokuapp.com/dynamic_content?with_content=static");
final WebElement element = driver.findElement(LOCATOR_DYNAMIC_CONTENT);
if (!retryingFindClick(LOCATOR_REFRESH)) {
Assertions.fail("Element is still stale after 5 attempts");
}
Assertions.assertDoesNotThrow(() -> retryingFindGetText(LOCATOR_DYNAMIC_CONTENT));
}
private boolean retryingFindClick(By locator) {
boolean result = false;
int attempts = 0;
while (attempts < 5) {
try {
driver.findElement(locator).click();
result = true;
break;
} catch (StaleElementReferenceException ex) {
System.out.println(ex.getMessage());
}
attempts++;
}
return result;
}
private String retryingFindGetText(By locator) {
String result = null;
int attempts = 0;
while (attempts < 5) {
try {
result = driver.findElement(locator).getText();
break;
} catch (StaleElementReferenceException ex) {
System.out.println(ex.getMessage());
}
attempts++;
}
return result;
}
@AfterEach
void teardown() {
if (driver != null) {
driver.quit();
driver = null;
}
}
}