Merge pull request #13850 from AndiCover/BAEL-5739_2
BAEL-5739 Avoiding the StaleElementReferenceException in Selenium
This commit is contained in:
commit
bf3b038daa
|
@ -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