BAEL-5739 Avoiding the StaleElementReferenceException in Selenium
This commit is contained in:
parent
8b674cd074
commit
0ea86e69d9
|
@ -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();
|
||||||
|
}
|
||||||
|
}
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue