ฉันกำลังใช้การทดสอบซีลีเนียมจำนวนมากโดยใช้ Java บางครั้งการทดสอบของฉันล้มเหลวเนื่องจากไฟล์StaleElementReferenceException
. คุณช่วยแนะนำแนวทางในการทำให้การทดสอบมีเสถียรภาพมากขึ้นได้ไหม
ฉันกำลังใช้การทดสอบซีลีเนียมจำนวนมากโดยใช้ Java บางครั้งการทดสอบของฉันล้มเหลวเนื่องจากไฟล์StaleElementReferenceException
. คุณช่วยแนะนำแนวทางในการทำให้การทดสอบมีเสถียรภาพมากขึ้นได้ไหม
คำตอบ:
สิ่งนี้สามารถเกิดขึ้นได้หากการดำเนินการ DOM ที่เกิดขึ้นบนเพจทำให้ไม่สามารถเข้าถึงองค์ประกอบได้ชั่วคราว ในการอนุญาตสำหรับกรณีเหล่านั้นคุณสามารถลองเข้าถึงองค์ประกอบหลาย ๆ ครั้งแบบวนซ้ำก่อนที่จะโยนข้อยกเว้นในที่สุด
ลองใช้วิธีแก้ปัญหาที่ยอดเยี่ยมจาก 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;
}
ฉันมีปัญหานี้เป็นระยะ ๆ ไม่เป็นที่รู้จักสำหรับฉัน BackboneJS กำลังทำงานอยู่บนเพจและแทนที่องค์ประกอบที่ฉันพยายามคลิก รหัสของฉันมีลักษณะเช่นนี้
driver.findElement(By.id("checkoutLink")).click();
ซึ่งแน่นอนว่าการทำงานจะเหมือนกับสิ่งนี้
WebElement checkoutLink = driver.findElement(By.id("checkoutLink"));
checkoutLink.click();
สิ่งที่จะเกิดขึ้นเป็นครั้งคราวคือจาวาสคริปต์จะแทนที่องค์ประกอบ checkoutLink ระหว่างการค้นหาและคลิกกล่าวคือ
WebElement checkoutLink = driver.findElement(By.id("checkoutLink"));
// javascript replaces checkoutLink
checkoutLink.click();
ซึ่งนำไปสู่ StaleElementReferenceException อย่างถูกต้องเมื่อพยายามคลิกลิงก์ ฉันไม่พบวิธีที่เชื่อถือได้ในการบอกให้ WebDriver รอจนกว่าจาวาสคริปต์จะทำงานเสร็จสิ้นดังนั้นนี่คือวิธีที่ฉันแก้ไขในที่สุด
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;
}
});
รหัสนี้จะพยายามคลิกลิงก์อย่างต่อเนื่องโดยไม่สนใจ StaleElementReferenceExceptions จนกว่าจะคลิกสำเร็จหรือหมดเวลา ฉันชอบโซลูชันนี้เพราะช่วยให้คุณไม่ต้องเขียนตรรกะการลองใหม่และใช้เฉพาะโครงสร้างในตัวของ WebDriver
โดยทั่วไปเกิดจากการอัปเดต DOM และคุณพยายามเข้าถึงองค์ประกอบที่อัปเดต / ใหม่ - แต่ DOM ได้รับการรีเฟรชจึงเป็นการอ้างอิงที่ไม่ถูกต้องที่คุณมี ..
หลีกเลี่ยงปัญหานี้โดยใช้การรออย่างชัดเจนในองค์ประกอบก่อนเพื่อให้แน่ใจว่าการอัปเดตเสร็จสมบูรณ์จากนั้นรับข้อมูลอ้างอิงใหม่ไปยังองค์ประกอบอีกครั้ง
นี่คือบางส่วนรหัสหลอกเพื่อแสดงให้เห็น (ดัดแปลงมาจากบางรหัส C # จะใช้สำหรับตรงปัญหานี้):
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.
หวังว่านี่จะช่วยได้!
วิธีแก้ปัญหาของ Kenny นั้นดี แต่ก็สามารถเขียนได้อย่างสวยงามมากขึ้น
new WebDriverWait(driver, timeout)
.ignoring(StaleElementReferenceException.class)
.until((WebDriver d) -> {
d.findElement(By.id("checkoutLink")).click();
return true;
});
หรือยัง:
new WebDriverWait(driver, timeout).ignoring(StaleElementReferenceException.class).until(ExpectedConditions.elementToBeClickable(By.id("checkoutLink")));
driver.findElement(By.id("checkoutLink")).click();
แต่อย่างไรก็ตามทางออกที่ดีที่สุดคือการใช้ห้องสมุด Selenide ซึ่งจัดการเรื่องประเภทนี้และอื่น ๆ (แทนที่จะใช้การอ้างอิงองค์ประกอบจะจัดการกับพร็อกซีดังนั้นคุณจึงไม่ต้องจัดการกับองค์ประกอบเก่าซึ่งอาจเป็นเรื่องยาก) เซเลไนด์
สาเหตุที่ทำให้StaleElementReferenceException
เกิดเหตุการณ์ดังกล่าวได้ถูกวางไว้แล้ว: การอัปเดต DOM ระหว่างการค้นหาและทำบางสิ่งกับองค์ประกอบ
สำหรับปัญหาการคลิกฉันเพิ่งใช้วิธีแก้ปัญหาเช่นนี้:
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();
}
ส่วนที่สำคัญคือ "การผูกมัด" ของซีลีเนียมเองExpectedConditions
ผ่านทางExpectedConditions.refreshed()
. สิ่งนี้จะรอและตรวจสอบว่าองค์ประกอบที่เป็นปัญหาได้รับการรีเฟรชในช่วงระยะหมดเวลาที่กำหนดหรือไม่และรอให้องค์ประกอบนั้นคลิกได้
ในโครงการของฉันฉันได้แนะนำแนวคิดเกี่ยวกับ StableWebElement เป็น Wrapper สำหรับ WebElement ซึ่งสามารถตรวจจับได้ว่าองค์ประกอบเป็น Stale หรือไม่และค้นหาการอ้างอิงใหม่ไปยังองค์ประกอบเดิม ฉันได้เพิ่มวิธีการช่วยเหลือในการค้นหาองค์ประกอบที่ส่งคืน StableWebElement แทน WebElement และปัญหาเกี่ยวกับ StaleElementReference ก็หายไป
public static IStableWebElement FindStableElement(this ISearchContext context, By by)
{
var element = context.FindElement(by);
return new StableWebElement(context, element, by, SearchApproachType.First);
}
รหัสใน C # มีอยู่ในหน้าโครงการของฉัน แต่สามารถย้ายไปยัง java ได้อย่างง่ายดายhttps://github.com/cezarypiatek/Tellurium/blob/master/Src/MvcPages/SeleniumUtils/StableWebElement.cs
วิธีแก้ปัญหาใน C # คือ:
ระดับตัวช่วย:
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;
}
}
}
การร้องขอ:
DriverHelper driverHelper = new DriverHelper("http://www.seleniumhq.org/docs/04_webdriver_advanced.jsp", 10);
driverHelper.ClickElement("input[value='csharp']:first-child");
ในทำนองเดียวกันสามารถใช้สำหรับ Java
โซลูชันของ Kenny เลิกใช้แล้วให้ใช้สิ่งนี้ฉันใช้คลาสการดำเนินการเพื่อดับเบิลคลิก แต่คุณสามารถทำอะไรก็ได้
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
StaleElementReference
นี่ขึ้นอยู่กับคำตอบของ jspcalเป็นอย่างมากแต่ฉันต้องแก้ไขคำตอบนั้นเพื่อให้มันทำงานได้อย่างสมบูรณ์กับการตั้งค่าของเราดังนั้นฉันจึงต้องการเพิ่มที่นี่ในกรณีที่เป็นประโยชน์กับผู้อื่น ถ้าคำตอบนี้ช่วยให้คุณโปรดไป upvote คำตอบ 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.
}
}
}
สิ่งนี้ใช้ได้กับฉัน (ใช้งานได้ 100%) โดยใช้ 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;
}
ปัญหาคือเมื่อคุณส่งองค์ประกอบจาก Javascript ไปยัง Java กลับไปที่ Javascript ก็จะสามารถออกจาก DOM ได้
ลองทำทุกอย่างใน Javascript:
driver.executeScript("document.querySelector('#my_id').click()")
ลองทำตามนี้
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
}
}
ฉันพบวิธีแก้ปัญหาที่นี่แล้ว ในองค์ประกอบกรณีของฉันไม่สามารถเข้าถึงได้ในกรณีที่ออกจากหน้าต่างแท็บหรือเพจปัจจุบันและกลับมาอีกครั้ง
.ignoring (StaleElement ... ), .refreshed (... ) และ elementToBeClicable (... ) ไม่ได้ช่วยและฉันได้รับข้อยกเว้นในact.doubleClick(element).build().perform();
สตริง
การใช้ฟังก์ชันในคลาสทดสอบหลักของฉัน:
openForm(someXpath);
ฟังก์ชัน BaseTest ของฉัน:
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;
}
}
ฟังก์ชัน BaseClass ของฉัน:
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))));
}
อาจมีปัญหาที่อาจนำไปสู่ StaleElementReferenceException ที่ยังไม่มีใครกล่าวถึง (เกี่ยวกับการดำเนินการ)
ฉันอธิบายใน Javascript แต่มันเหมือนกันใน Java
สิ่งนี้ใช้ไม่ได้:
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()
แต่การสร้างอินสแตนซ์การดำเนินการอีกครั้งจะช่วยแก้ปัญหาได้:
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()
โดยปกติแล้ว StaleElementReferenceException เมื่อองค์ประกอบที่เราพยายามเข้าถึงปรากฏขึ้น แต่องค์ประกอบอื่น ๆ อาจส่งผลต่อตำแหน่งขององค์ประกอบที่เราสนใจด้วยเหตุนี้เมื่อเราพยายามคลิกหรือ getText หรือพยายามดำเนินการบางอย่างบน WebElement เราจะได้รับข้อยกเว้นซึ่งโดยปกติจะระบุว่าองค์ประกอบไม่ได้แนบมากับ DOM .
วิธีแก้ไขที่ฉันลองมีดังนี้:
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));
}
ในโค้ดด้านบนก่อนอื่นฉันพยายามรอแล้วคลิกที่องค์ประกอบหากมีข้อยกเว้นเกิดขึ้นจากนั้นฉันก็จับมันและลองวนซ้ำเนื่องจากมีความเป็นไปได้ที่องค์ประกอบทั้งหมดอาจไม่สามารถโหลดได้และอาจมีข้อยกเว้นอีกครั้ง
อาจจะเพิ่งเพิ่มเมื่อเร็ว ๆ นี้ แต่คำตอบอื่น ๆ ไม่สามารถพูดถึงคุณสมบัติการรอโดยปริยายของซีลีเนียมซึ่งทำทุกอย่างข้างต้นให้คุณและสร้างไว้ในซีลีเนียม
driver.manage().timeouts().implicitlyWait(10,TimeUnit.SECONDS);
การดำเนินการนี้จะลองfindElement()
เรียกอีกครั้งจนกว่าจะพบองค์ประกอบหรือเป็นเวลา 10 วินาที
ที่มา - http://www.seleniumhq.org/docs/04_webdriver_advanced.jsp