Я реализую множество тестов Selenium с использованием Java. Иногда мои тесты терпят неудачу из-за файла StaleElementReferenceException
. Не могли бы вы предложить какие-нибудь подходы к повышению стабильности тестов?
Я реализую множество тестов Selenium с использованием 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();
Иногда происходило то, что javascript заменял элемент checkoutLink между поиском и щелчком по нему, т.е.
WebElement checkoutLink = driver.findElement(By.id("checkoutLink"));
// javascript replaces checkoutLink
checkoutLink.click();
Что по праву привело к исключению StaleElementReferenceException при попытке щелкнуть ссылку. Я не мог найти надежного способа сказать WebDriver, чтобы он дождался завершения работы javascript, поэтому вот как я в конечном итоге решил это.
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.
Надеюсь это поможет!
Решение Кенни хорошее, но его можно написать более элегантно
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();
}
Важнейшей частью является «цепочка» собственного Selenium ExpectedConditions
через ExpectedConditions.refreshed()
. Это фактически ожидает и проверяет, обновился ли рассматриваемый элемент в течение указанного тайм-аута, а также ожидает, когда элемент станет интерактивным.
Посмотрите документацию по обновленному методу .
В своем проекте я ввел понятие StableWebElement. Это оболочка для WebElement, которая может определять, является ли элемент устаревшим, и находить новую ссылку на исходный элемент. Я добавил вспомогательные методы для поиска элементов, которые возвращают 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.
Решение Кенни устарело, используйте это, я использую класс действий для двойного щелчка, но вы можете делать что угодно.
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, но мне пришлось изменить этот ответ, чтобы он работал чисто с нашей настройкой, и поэтому я хотел добавить его здесь, если он будет полезен другим. Если этот ответ вам помог, пожалуйста, проголосуйте за ответ 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% работает) с использованием С #
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, когда элемент, к которому мы пытаемся получить доступ, появился, но другие элементы могут повлиять на положение интересующего нас элемента, поэтому, когда мы пытаемся щелкнуть или получить текст, или попытаться выполнить какое-либо действие с 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));
}
В приведенном выше коде я сначала пытаюсь подождать, а затем щелкнуть элемент, если возникает исключение, затем я ловлю его и пытаюсь зациклить его, поскольку есть вероятность, что все элементы могут не быть загружены, и снова может возникнуть исключение.
Возможно, он был добавлен совсем недавно, но в других ответах не упоминается функция неявного ожидания Selenium, которая выполняет все вышеперечисленное за вас и встроена в Selenium.
driver.manage().timeouts().implicitlyWait(10,TimeUnit.SECONDS);
Это повторит попытку findElement()
вызовы, пока элемент не будет найден, или в течение 10 секунд.
Источник - http://www.seleniumhq.org/docs/04_webdriver_advanced.jsp