Tôi đang thực hiện rất nhiều thử nghiệm Selenium bằng Java. Đôi khi, các bài kiểm tra của tôi không thành công do a StaleElementReferenceException
. Bạn có thể đề xuất một số cách tiếp cận để làm cho các bài kiểm tra ổn định hơn không?
Tôi đang thực hiện rất nhiều thử nghiệm Selenium bằng Java. Đôi khi, các bài kiểm tra của tôi không thành công do a StaleElementReferenceException
. Bạn có thể đề xuất một số cách tiếp cận để làm cho các bài kiểm tra ổn định hơn không?
Câu trả lời:
Điều này có thể xảy ra nếu một thao tác DOM xảy ra trên trang tạm thời khiến phần tử không thể truy cập được. Để cho phép những trường hợp đó, bạn có thể cố gắng truy cập phần tử nhiều lần trong một vòng lặp trước khi cuối cùng đưa ra một ngoại lệ.
Hãy thử giải pháp tuyệt vời này từ darrelgrainger.blogspot.com :
public boolean retryingFindClick(By by) {
boolean result = false;
int attempts = 0;
while(attempts < 2) {
try {
driver.findElement(by).click();
result = true;
break;
} catch(StaleElementException e) {
}
attempts++;
}
return result;
}
Tôi đã gặp vấn đề này không liên tục. Tôi không biết, BackboneJS đang chạy trên trang và thay thế phần tử mà tôi đang cố nhấp vào. Mã của tôi trông như thế này.
driver.findElement(By.id("checkoutLink")).click();
Tất nhiên là về mặt chức năng như thế này.
WebElement checkoutLink = driver.findElement(By.id("checkoutLink"));
checkoutLink.click();
Điều đôi khi sẽ xảy ra là javascript sẽ thay thế phần tử checkoutLink giữa việc tìm và nhấp vào nó, tức là.
WebElement checkoutLink = driver.findElement(By.id("checkoutLink"));
// javascript replaces checkoutLink
checkoutLink.click();
Điều này đã dẫn đến một StaleElementReferenceException một cách hợp lý khi cố gắng nhấp vào liên kết. Tôi không thể tìm thấy bất kỳ cách đáng tin cậy nào để yêu cầu WebDriver đợi cho đến khi javascript chạy xong, vì vậy đây là cách cuối cùng tôi đã giải quyết nó.
new WebDriverWait(driver, timeout)
.ignoring(StaleElementReferenceException.class)
.until(new Predicate<WebDriver>() {
@Override
public boolean apply(@Nullable WebDriver driver) {
driver.findElement(By.id("checkoutLink")).click();
return true;
}
});
Mã này sẽ liên tục cố gắng nhấp vào liên kết, bỏ qua StaleElementReferenceExceptions cho đến khi nhấp chuột thành công hoặc đạt đến thời gian chờ. Tôi thích giải pháp này vì nó giúp bạn không phải viết bất kỳ logic thử lại nào và chỉ sử dụng các cấu trúc tích hợp của WebDriver.
Nói chung, điều này là do DOM đang được cập nhật và bạn đang cố gắng truy cập vào một phần tử được cập nhật / mới - nhưng DOM đã được làm mới nên đó là một tham chiếu không hợp lệ mà bạn có ..
Giải quyết vấn đề này trước tiên bằng cách chờ đợi rõ ràng đối với phần tử để đảm bảo cập nhật hoàn tất, sau đó lấy lại tham chiếu mới cho phần tử.
Dưới đây là một số mã psuedo để minh họa (Phỏng theo một số mã C # mà tôi sử dụng cho CHÍNH XÁC vấn đề này):
WebDriverWait wait = new WebDriverWait(browser, TimeSpan.FromSeconds(10));
IWebElement aRow = browser.FindElement(By.XPath(SOME XPATH HERE);
IWebElement editLink = aRow.FindElement(By.LinkText("Edit"));
//this Click causes an AJAX call
editLink.Click();
//must first wait for the call to complete
wait.Until(ExpectedConditions.ElementExists(By.XPath(SOME XPATH HERE));
//you've lost the reference to the row; you must grab it again.
aRow = browser.FindElement(By.XPath(SOME XPATH HERE);
//now proceed with asserts or other actions.
Hi vọng điêu nay co ich!
Giải pháp của Kenny là tốt, tuy nhiên nó có thể được viết theo cách thanh lịch hơn
new WebDriverWait(driver, timeout)
.ignoring(StaleElementReferenceException.class)
.until((WebDriver d) -> {
d.findElement(By.id("checkoutLink")).click();
return true;
});
Hoặc cũng có thể:
new WebDriverWait(driver, timeout).ignoring(StaleElementReferenceException.class).until(ExpectedConditions.elementToBeClickable(By.id("checkoutLink")));
driver.findElement(By.id("checkoutLink")).click();
Nhưng dù sao, giải pháp tốt nhất là dựa vào thư viện Selenide, nó xử lý những thứ này và hơn thế nữa. (thay vì tham chiếu phần tử, nó xử lý proxy để bạn không bao giờ phải xử lý các phần tử cũ, điều này có thể khá khó khăn). Selenide
Lý do tại sao điều StaleElementReferenceException
này xảy ra đã được đưa ra: các cập nhật cho DOM giữa việc tìm kiếm và thực hiện điều gì đó với phần tử.
Đối với vấn đề nhấp chuột, gần đây tôi đã sử dụng một giải pháp như sau:
public void clickOn(By locator, WebDriver driver, int timeout)
{
final WebDriverWait wait = new WebDriverWait(driver, timeout);
wait.until(ExpectedConditions.refreshed(
ExpectedConditions.elementToBeClickable(locator)));
driver.findElement(locator).click();
}
Phần quan trọng là "chuỗi" của riêng Selenium ExpectedConditions
thông qua ExpectedConditions.refreshed()
. Điều này thực sự chờ đợi và kiểm tra xem phần tử được đề cập đã được làm mới trong thời gian chờ được chỉ định hay chưa và cũng đợi phần tử đó có thể nhấp được.
Hãy xem tài liệu về phương pháp được làm mới .
Trong dự án của mình, tôi đã giới thiệu một khái niệm về StableWebElement. Nó là một trình bao bọc cho WebElement có thể phát hiện xem phần tử có phải là cũ hay không và tìm một tham chiếu mới đến phần tử gốc. Tôi đã thêm một phương thức trợ giúp để định vị các phần tử trả về StableWebElement thay vì WebElement và sự cố với StaleElementReference đã biến mất.
public static IStableWebElement FindStableElement(this ISearchContext context, By by)
{
var element = context.FindElement(by);
return new StableWebElement(context, element, by, SearchApproachType.First);
}
Mã trong C # có sẵn trên trang dự án của tôi nhưng nó có thể dễ dàng được chuyển sang java https://github.com/cezarypiatek/Tellurium/blob/master/Src/MvcPages/SeleniumUtils/StableWebElement.cs
Một giải pháp trong C # sẽ là:
Lớp người trợ giúp:
internal class DriverHelper
{
private IWebDriver Driver { get; set; }
private WebDriverWait Wait { get; set; }
public DriverHelper(string driverUrl, int timeoutInSeconds)
{
Driver = new ChromeDriver();
Driver.Url = driverUrl;
Wait = new WebDriverWait(Driver, TimeSpan.FromSeconds(timeoutInSeconds));
}
internal bool ClickElement(string cssSelector)
{
//Find the element
IWebElement element = Wait.Until(d=>ExpectedConditions.ElementIsVisible(By.CssSelector(cssSelector)))(Driver);
return Wait.Until(c => ClickElement(element, cssSelector));
}
private bool ClickElement(IWebElement element, string cssSelector)
{
try
{
//Check if element is still included in the dom
//If the element has changed a the OpenQA.Selenium.StaleElementReferenceException is thrown.
bool isDisplayed = element.Displayed;
element.Click();
return true;
}
catch (StaleElementReferenceException)
{
//wait until the element is visible again
element = Wait.Until(d => ExpectedConditions.ElementIsVisible(By.CssSelector(cssSelector)))(Driver);
return ClickElement(element, cssSelector);
}
catch (Exception)
{
return false;
}
}
}
Lời mời:
DriverHelper driverHelper = new DriverHelper("http://www.seleniumhq.org/docs/04_webdriver_advanced.jsp", 10);
driverHelper.ClickElement("input[value='csharp']:first-child");
Tương tự có thể được sử dụng cho Java.
Giải pháp của Kenny không được dùng nữa, hãy sử dụng giải pháp này, tôi đang sử dụng lớp hành động để nhấp đúp nhưng bạn có thể làm bất cứ điều gì.
new FluentWait<>(driver).withTimeout(30, TimeUnit.SECONDS).pollingEvery(5, TimeUnit.SECONDS)
.ignoring(StaleElementReferenceException.class)
.until(new Function() {
@Override
public Object apply(Object arg0) {
WebElement e = driver.findelement(By.xpath(locatorKey));
Actions action = new Actions(driver);
action.moveToElement(e).doubleClick().perform();
return true;
}
});
findByAndroidId
Phương pháp sạch sẽ xử lý một cách duyên dáng StaleElementReference
.Điều này chủ yếu dựa trên câu trả lời của jspcal nhưng tôi đã phải sửa đổi câu trả lời đó để làm cho nó hoạt động rõ ràng với thiết lập của chúng tôi và vì vậy tôi muốn thêm nó vào đây trong trường hợp nó hữu ích cho người khác. Nếu câu trả lời này giúp ích cho bạn, vui lòng ủng hộ câu trả lời của jspcal .
// This loops gracefully handles StateElementReference errors and retries up to 10 times. These can occur when an element, like a modal or notification, is no longer available.
export async function findByAndroidId( id, { assert = wd.asserters.isDisplayed, timeout = 10000, interval = 100 } = {} ) {
MAX_ATTEMPTS = 10;
let attempt = 0;
while( attempt < MAX_ATTEMPTS ) {
try {
return await this.waitForElementById( `android:id/${ id }`, assert, timeout, interval );
}
catch ( error ) {
if ( error.message.includes( "StaleElementReference" ) )
attempt++;
else
throw error; // Re-throws the error so the test fails as normal if the assertion fails.
}
}
}
Điều này phù hợp với tôi (100% hoạt động) bằng cách sử dụng C #
public Boolean RetryingFindClick(IWebElement webElement)
{
Boolean result = false;
int attempts = 0;
while (attempts < 2)
{
try
{
webElement.Click();
result = true;
break;
}
catch (StaleElementReferenceException e)
{
Logging.Text(e.Message);
}
attempts++;
}
return result;
}
Vấn đề là vào thời điểm bạn chuyển phần tử từ Javascript sang Java trở lại Javascript, nó có thể đã rời khỏi DOM.
Hãy thử làm toàn bộ điều này trong Javascript:
driver.executeScript("document.querySelector('#my_id').click()")
Thử đi
while (true) { // loops forever until break
try { // checks code for exceptions
WebElement ele=
(WebElement)wait.until(ExpectedConditions.elementToBeClickable((By.xpath(Xpath))));
break; // if no exceptions breaks out of loop
}
catch (org.openqa.selenium.StaleElementReferenceException e1) {
Thread.sleep(3000); // you can set your value here maybe 2 secs
continue; // continues to loop if exception is found
}
}
Tôi đã tìm thấy giải pháp ở đây . Trong trường hợp của tôi, phần tử không thể truy cập được trong trường hợp rời khỏi cửa sổ, tab hoặc trang hiện tại và quay lại lần nữa.
.ignoring (StaleElement ...), .refreshed (...) và elementToBeClicable (...) không giúp được gì và tôi đã nhận được ngoại lệ trên act.doubleClick(element).build().perform();
chuỗi.
Sử dụng hàm trong lớp thử nghiệm chính của tôi:
openForm(someXpath);
Chức năng BaseTest của tôi:
int defaultTime = 15;
boolean openForm(String myXpath) throws Exception {
int count = 0;
boolean clicked = false;
while (count < 4 || !clicked) {
try {
WebElement element = getWebElClickable(myXpath,defaultTime);
act.doubleClick(element).build().perform();
clicked = true;
print("Element have been clicked!");
break;
} catch (StaleElementReferenceException sere) {
sere.toString();
print("Trying to recover from: "+sere.getMessage());
count=count+1;
}
}
Hàm BaseClass của tôi:
protected WebElement getWebElClickable(String xpath, int waitSeconds) {
wait = new WebDriverWait(driver, waitSeconds);
return wait.ignoring(StaleElementReferenceException.class).until(
ExpectedConditions.refreshed(ExpectedConditions.elementToBeClickable(By.xpath(xpath))));
}
Có thể có một vấn đề tiềm ẩn dẫn đến StaleElementReferenceException mà không ai đề cập đến cho đến nay (liên quan đến các hành động).
Tôi giải thích nó bằng Javascript, nhưng nó cũng giống như vậy trong Java.
Điều này sẽ không hoạt động:
let actions = driver.actions({ bridge: true })
let a = await driver.findElement(By.css('#a'))
await actions.click(a).perform() // this leads to a DOM change, #b will be removed and added again to the DOM.
let b = await driver.findElement(By.css('#b'))
await actions.click(b).perform()
Nhưng việc khởi tạo lại các hành động sẽ giải quyết được điều đó:
let actions = driver.actions({ bridge: true })
let a = await driver.findElement(By.css('#a'))
await actions.click(a).perform() // this leads to a DOM change, #b will be removed and added again to the DOM.
actions = driver.actions({ bridge: true }) // new
let b = await driver.findElement(By.css('#b'))
await actions.click(b).perform()
Thông thường StaleElementReferenceException khi phần tử chúng tôi cố gắng truy cập đã xuất hiện nhưng các phần tử khác có thể ảnh hưởng đến vị trí của phần tử mà chúng tôi đang nắm giữ do đó khi chúng tôi cố gắng nhấp vào hoặc getText hoặc cố gắng thực hiện một số hành động trên WebElement, chúng tôi nhận được ngoại lệ thường cho biết phần tử không được đính kèm với DOM .
Giải pháp tôi đã thử như sau:
protected void clickOnElement(By by) {
try {
waitForElementToBeClickableBy(by).click();
} catch (StaleElementReferenceException e) {
for (int attempts = 1; attempts < 100; attempts++) {
try {
waitFor(500);
logger.info("Stale element found retrying:" + attempts);
waitForElementToBeClickableBy(by).click();
break;
} catch (StaleElementReferenceException e1) {
logger.info("Stale element found retrying:" + attempts);
}
}
}
protected WebElement waitForElementToBeClickableBy(By by) {
WebDriverWait wait = new WebDriverWait(getDriver(), 10);
return wait.until(ExpectedConditions.elementToBeClickable(by));
}
Trong đoạn mã trên, trước tiên, tôi cố gắng đợi và sau đó nhấp vào phần tử nếu ngoại lệ xảy ra sau đó tôi bắt nó và cố gắng lặp lại nó vì có khả năng tất cả các phần tử vẫn không được tải và một lần nữa có thể xảy ra ngoại lệ.
Có thể nó đã được thêm vào gần đây, nhưng các câu trả lời khác không đề cập đến tính năng chờ ngầm của Selenium, thực hiện tất cả những điều trên cho bạn và được tích hợp sẵn trong Selenium.
driver.manage().timeouts().implicitlyWait(10,TimeUnit.SECONDS);
Thao tác này sẽ thử lại findElement()
các lệnh gọi cho đến khi phần tử được tìm thấy hoặc trong 10 giây.
Nguồn - http://www.seleniumhq.org/docs/04_webdriver_advanced.jsp