Selenium WebDriver Как решить проблему исключения устаревших элементов?
У меня есть следующий код в тесте Selenium 2 Web Driver, который работает, когда я отлаживаю, но большую часть времени терпит неудачу, когда я запускаю его в сборке. Я знаю, что это должно быть связано с тем, как страница не обновляется, но не знает, как ее разрешить, поэтому любые рекомендации относительно того, что я сделал неправильно, оцениваются. Я использую JSF-интерфейсы в качестве платформы веб-приложений. Когда я нажимаю на ссылку "Добавить новую ссылку", появляется всплывающее диалоговое окно с полем ввода, в котором я могу ввести дату, а затем нажмите "Сохранить". Он заключается в том, что элемент ввода вводит текст, чтобы получить исключение элементарного элемента ref.
Заранее спасибо
import static org.junit.Assert.assertEquals;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.junit.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.support.ui.ExpectedCondition;
import org.openqa.selenium.support.ui.WebDriverWait;
public class EnterActiveSubmissionIntegrationTest {
Map<String, Map<String, String>> tableData = new HashMap<String, Map<String, String>>();
@Test
public void testEnterActiveSubmission() throws Exception {
// Create a new instance of the Firefox driver
// Notice that the remainder of the code relies on the interface,
// not the implementation.
System.setProperty("webdriver.chrome.driver", "C:/apps/chromedriver.exe");
WebDriver driver = new ChromeDriver();
// And now use this to visit Google
driver.get("http://localhost:8080/strfingerprinting");
// Alternatively the same thing can be done like this
// driver.navigate().to("http://www.google.com");
// Find the text input element by its name
WebElement element = driver.findElement(By.linkText("Manage Submissions"));
element.click();
parseTableData(driver, "form:submissionDataTable_data", 1);
assertEquals(tableData.get("form:submissionDataTable_data").get("12"), "Archived");
WebElement newElement = driver.findElement(By.linkText("Add new"));
newElement.click();
WebDriverWait wait = new WebDriverWait(driver,10);
wait.until(new ExpectedCondition<Boolean>() {
public Boolean apply(WebDriver driver) {
WebElement button = driver.findElement(By
.name("createForm:dateInput_input"));
if (button.isDisplayed())
return true;
else
return false;
}
});
WebElement textElement = driver.findElement(By.name("createForm:dateInput_input"));
textElement.sendKeys("24/04/2013");
WebElement saveElement = driver.findElement(By.name("createForm:saveButton"));
saveElement.click();
driver.navigate().refresh();
parseTableData(driver, "form:submissionDataTable_data", 2);
//Close the browser
driver.quit();
}
private void parseTableData(WebDriver driver, String id, int expectedRows) {
// Check the title of the page or expected element on page
WebElement subTableElement = driver.findElement(By.id(id));
List<WebElement> tr_collection=subTableElement.findElements(By.xpath("id('"+ id + "')/tr"));
assertEquals("incorrect number of rows returned", expectedRows, tr_collection.size());
int row_num,col_num;
row_num=1;
if(tableData.get(id) == null) {
tableData.put(id, new HashMap<String, String>());
}
Map<String, String> subTable = tableData.get(id);
for(WebElement trElement : tr_collection)
{
List<WebElement> td_collection=trElement.findElements(By.xpath("td"));
col_num=1;
for(WebElement tdElement : td_collection)
{
subTable.put(row_num + "" + col_num, tdElement.getText());
col_num++;
}
row_num++;
}
}
}
Когда я запускаю это, я получаю следующее исключение, но это может произойти на
WebElement textElement = driver.findElement(By.name("createForm:dateInput_input"));
или
if (button.isDisplayed())
трассировка исключений
org.openqa.selenium.StaleElementReferenceException: stale element reference: element is not attached to the page document
(Session info: chrome=26.0.1410.64)
(Driver info: chromedriver=0.8,platform=Windows NT 6.0 SP2 x86) (WARNING: The server did not provide any stacktrace information)
Command duration or timeout: 56 milliseconds
For documentation on this error, please visit: http://seleniumhq.org/exceptions/stale_element_reference.html
Build info: version: '2.32.0', revision: '6c40c187d01409a5dc3b7f8251859150c8af0bcb', time: '2013-04-09 10:39:28'
System info: os.name: 'Windows Vista', os.arch: 'x86', os.version: '6.0', java.version: '1.6.0_10'
Session ID: 784c53b99ad83c44d089fd04e9a42904
Driver info: org.openqa.selenium.chrome.ChromeDriver
Capabilities [{platform=XP, acceptSslCerts=true, javascriptEnabled=true, browserName=chrome, rotatable=false, driverVersion=0.8, locationContextEnabled=true, version=26.0.1410.64, cssSelectorsEnabled=true, databaseEnabled=true, handlesAlerts=true, browserConnectionEnabled=false, nativeEvents=true, webStorageEnabled=true, applicationCacheEnabled=false, takesScreenshot=true}]
at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:39)
at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:27)
at java.lang.reflect.Constructor.newInstance(Constructor.java:513)
at org.openqa.selenium.remote.ErrorHandler.createThrowable(ErrorHandler.java:187)
at org.openqa.selenium.remote.ErrorHandler.throwIfResponseFailed(ErrorHandler.java:145)
at org.openqa.selenium.remote.RemoteWebDriver.execute(RemoteWebDriver.java:554)
at org.openqa.selenium.remote.RemoteWebElement.execute(RemoteWebElement.java:268)
at org.openqa.selenium.remote.RemoteWebElement.isDisplayed(RemoteWebElement.java:320)
at com.integration.web.EnterActiveSubmissionIntegrationTest$1.apply(EnterActiveSubmissionIntegrationTest.java:58)
at com.integration.web.EnterActiveSubmissionIntegrationTest$1.apply(EnterActiveSubmissionIntegrationTest.java:1)
at org.openqa.selenium.support.ui.FluentWait.until(FluentWait.java:208)
at com.integration.web.EnterActiveSubmissionIntegrationTest.testEnterActiveSubmission(EnterActiveSubmissionIntegrationTest.java:53)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
Ответы
Ответ 1
Прежде всего, давайте проясним, что такое WebElement.
WebElement - это ссылка на элемент в DOM.
Вызывается StaleElementException, когда элемент, с которым вы взаимодействовали, уничтожается, а затем воссоздается. Большинство сложных веб-страниц в эти дни будут перемещать вещи "на лету", поскольку пользователь взаимодействует с ним, и для этого необходимо уничтожить и воссоздать элементы в DOM.
Когда это происходит, ссылка на элемент в DOM, который вы ранее имели, становится устаревшей, и вы больше не можете использовать эту ссылку для взаимодействия с элементом в DOM. Когда это произойдет, вам нужно будет обновить свою ссылку или в реальных условиях снова найти элемент.
Ответ 2
Это не проблема. Если вы закроете свой вызов .findElement в блоке try-catch и поймаете исключение StaleElementReferenceException, вы можете выполнить цикл и повторить попытку столько раз, сколько потребуется, пока это не удастся.
Вот несколько примеров, которые я написал.
Другой пример из Selenide project:
public static final Condition hidden = new Condition("hidden", true) {
@Override
public boolean apply(WebElement element) {
try {
return !element.isDisplayed();
} catch (StaleElementReferenceException elementHasDisappeared) {
return true;
}
}
};
Ответ 3
Что со мной произошло, так это то, что webdriver найдет ссылку на элемент DOM, а затем в какой-то момент после того, как эта ссылка будет получена, javascript удалит этот элемент и снова добавит его (потому что страница делала перерисовку, в основном).
Попробуйте это. Выясните действие, которое приводит к удалению элемента dom из DOM. В моем случае это был асинхронный вызов async, и элемент удалялся из DOM, когда вызов ajax был завершен. Сразу после этого действия подождите, пока элемент будет устаревшим:
... do a thing, possibly async, that should remove the element from the DOM ...
wait.until(ExpectedConditions.stalenessOf(theElement));
На этом этапе вы уверены, что элемент теперь устарел. Итак, в следующий раз, когда вы ссылаетесь на элемент, подождите еще раз, на этот раз ожидая его повторного добавления в DOM:
wait.until(ExpectedConditions.presenceOfElementLocated(By.id("whatever")))
Ответ 4
Попробуйте подождать такой элемент:
// Waiting 30 seconds for an element to be present on the page, checking
// for its presence once every 5 seconds.
Wait<WebDriver> stubbornWait = new FluentWait<WebDriver>(driver)
.withTimeout(30, SECONDS)
.pollingEvery(5, SECONDS)
.ignoring(NoSuchElementException.class)
.ignoring(StaleElementReferenceException.class);
WebElement foo = stubbornWait.until(new Function<WebDriver, WebElement>() {
public WebElement apply(WebDriver driver) {
return driver.findElement(By.id("foo"));
}
});
Ответ 5
Две причины для элемента Stale
-
Элемент, найденный на веб-странице, на который ссылается как WebElement в WebDriver, затем DOM изменяется (возможно, из-за функций JavaScript), что WebElement устарел.
-
Элемент полностью удален.
Когда вы пытаетесь взаимодействовать с staled WebElement [в любом случае], генерируется исключение StaleElementException.
Как избежать/разрешить Stale Exception?
- Сохранение локаторов в ваших элементах вместо ссылок
driver = webdriver.Firefox();
driver.get("http://www.github.com");
search_input = lambda: driver.find_element_by_name('q');
search_input().send_keys('hello world\n');
time.sleep(5);
search_input().send_keys('hello frank\n') // no stale element exception
- Использовать крючки в используемых библиотеках JS
# Using Jquery queue to get animation queue length.
animationQueueIs = """
return $.queue( $("#%s")[0], "fx").length;
""" % element_id
wait_until(lambda: self.driver.execute_script(animationQueueIs)==0)
- Перемещение ваших действий в JavaScript-инъекцию
self.driver.execute_script("$(\"li:contains('Narendra')\").click()");
- Проактивно ждать, пока элемент станет устаревшим
# Wait till the element goes stale, this means the list has updated
wait_until(lambda: is_element_stale(old_link_reference))
Это решение, которое работало для меня, я упомянул здесь, если у вас есть дополнительный сценарий, который работал для вас, а затем прокомментировал ниже
Ответ 6
StaleElementReferenceException связано с недоступностью элемента, к которому осуществляется доступ методом findelement.
Вам нужно убедиться, что перед выполнением каких-либо операций над элементом (если у вас есть сомнения в доступности этого элемента)
Ожидание видимости элемента
(new WebDriverWait(driver, 10)).until(new ExpectedCondition()
{
public Boolean apply(WebDriver d) {
return d.findElement(By.name("createForm:dateInput_input")).isDisplayed();
}});
Или иначе Используйте эту логику, чтобы проверить, присутствует ли этот элемент или нет.
Ответ 7
Используйте ожидаемые условия, предоставленные Selenium, чтобы ждать WebElement.
При отладке клиент работает не так быстро, как если бы вы запускали unit test или сборку maven.
Это означает, что в режиме отладки у клиента больше времени на подготовку элемента, но если сборка работает с тем же кодом, он намного быстрее, и WebElement, который вы ищете, может не отображаться в DOM страницы.
Поверьте мне, у меня была та же проблема.
например:
inClient.waitUntil(ExpectedConditions.visibilityOf(YourElement,2000))
Этот простой метод вызывает ожидание после его вызова в течение 2 секунд на видимость вашего WebElement в DOM.
Ответ 8
Поверьте мне, исключение StaleElement может быть очень расстраивающим... особенно при работе с драйвером chrome... но теперь единственный способ обойти это просто использовать то, что я называю Raw wait:
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
Хотя пуристы могут сказать, что мы используем магические числа, но как только вы знаете безопасную величину ожидания, я знал, что она эффективна в более чем 95% случаях.
Ответ 9
Я бы предложил не использовать @CachelookUp
для Selenium WebDriver для StaleElementReferenceException
.
Если вы используете аннотацию @FindBy
и имеете @CachelookUp
, просто прокомментируйте ее и проверьте.
Ответ 10
Я решил эту проблему со следующим кодом.
public WebElement waitForElement(final By findBy, final int waitTime) {
Wait<AppiumDriver> wait = new FluentWait<>((AppiumDriver) driver)
.withTimeout(waitTime, TimeUnit.SECONDS)
.pollingEvery(POLL_TIME, TimeUnit.SECONDS)
.ignoring(NoSuchElementException.class,StaleElementReferenceException.class);
WebElement webElement = wait.until(new Function<AppiumDriver, WebElement>() {
@Override
public WebElement apply(AppiumDriver driver) {
System.out.println("Trying to find element " + findBy.toString());
WebElement element = driver.findElement(findBy);
return element;
}
});
return webElement;
}
Ответ 11
Это сработало для меня (источник здесь):
/**
* Attempts to click on an element multiple times (to avoid stale element
* exceptions caused by rapid DOM refreshes)
*
* @param d
* The WebDriver
* @param by
* By element locator
*/
public static void dependableClick(WebDriver d, By by)
{
final int MAXIMUM_WAIT_TIME = 10;
final int MAX_STALE_ELEMENT_RETRIES = 5;
WebDriverWait wait = new WebDriverWait(d, MAXIMUM_WAIT_TIME);
int retries = 0;
while (true)
{
try
{
wait.until(ExpectedConditions.elementToBeClickable(by)).click();
return;
}
catch (StaleElementReferenceException e)
{
if (retries < MAX_STALE_ELEMENT_RETRIES)
{
retries++;
continue;
}
else
{
throw e;
}
}
}
}
Ответ 12
WebDriver должен ждать, пока элемент не будет установлен, а таймаут - через 10 секунд.
WebElement myDynamicElement1 = new WebDriverWait(driver, 10).until(
ExpectedConditions.presenceOfElementLocated(
By.name("createForm:dateInput_input")
)
);
Ответ 13
Пожалуйста, не путайте других среди нас, если мы не уверены в ответах. Это довольно неприятно для конечного пользователя. Простой и короткий ответ
используйте аннотацию @CacheLookup в webdriver. См. Ссылку ниже.
Как работает @CacheLookup в WebDriver?
Ответ 14
Использовать webdriverwait с ExpectedCondition в блоке try catch с циклом for
EX: для python
for i in range(4):
try:
element = WebDriverWait(driver, 120).until( \
EC.presence_of_element_located((By.XPATH, 'xpath')))
element.click()
break
except StaleElementReferenceException:
print "exception "
Ответ 15
В отношении ответа, данного @djangofan, похоже, что жизнеспособным решением является сохранение вашего кода внутри блока try catch
, где возникает возможная Staleness. Когда я использую этот ниже код, я не получал проблему в любое время.
public void inputName(String name)
{
try {
waitForVisibilityElement(name);//My own visibility function
findElement(By.name("customerName")).sendKeys(name);
}
catch (StaleElementReferenceException e)
{
e.getMessage();
}
}
Я пробовал использовать ExpectedConditions.presenceOfElementLocated(By)
, но исключения затухания все же бросаются с перерывами.
Надеемся, что это решение поможет.
Ответ 16
Это решение отлично справилось со мной:
Добавление функции обработки ошибок и повторите попытку
var pollLoop = function () {
element(by.id('spinnerElem')).getAttribute('class').then(function (cls) {
if (cls.indexOf('spinner-active') > -1) {
// wait for the spinner
} else {
//do your logic
promise.defer().fulfill();
}
}, function () {
// This err handling function is to handle the {StaleElementReferenceError} and makes sure we find the element always.
pollLoop();
});
};
Ответ 17
После глубокого исследования проблемы я обнаружил, что возникает ошибка при выборе элементов DIV, которые были добавлены только для Bootstrap. Браузер Chrome удаляет такие DIVS и возникает ошибка. Достаточно уйти и выбрать реальный элемент для исправления ошибки. Например, мой модальный диалог имеет структуру:
<div class="modal-content" uib-modal-transclude="">
<div class="modal-header">
...
</div>
<div class="modal-body">
<form class="form-horizontal ...">
...
</form>
<div>
<div>
Выбор div class= "модального тела" генерирует ошибку, выбор формы... работает так, как если бы он был.
Ответ 18
В моем случае эта ошибка была вызвана тем, что я определял элемент ActionChains вне
def parse(self, response):
при использовании комбинации Selenium и Scrapy, например:
Не работает:
class MySpider(scrapy.Spider):
action_chains = ActionChains(self.driver)
Перемещение action_chains = ActionChains(self.driver)
внутри def parse(self, response):
решило проблему, например:
Работает:
def parse(self, response):
self.driver.get(response.url)
action_chains = ActionChains(self.driver)
Ответ 19
Просто скачайте новое расширение chrome и используйте сервер selenium 3, он будет работать нормально.
Ответ 20
Лучший способ найти ссылки на устаревшие элементы - не использовать файл PageFactory, а вместо этого хранить локаторы (т.е. по элементам).
public class WebDriverFactory {
// if you want to multithread tests, use a ThreadLocal<WebDriver>
// instead.
// This also makes it so you don't have to pass around WebDriver objects
// when instantiating new Page classes
private static WebDriver driver = null;
public static WebDriver getDriver() {
return driver;
}
public static void setDriver(WebDriver browser) {
driver = browser;
}
}
// class to let me avoid typing out the lengthy driver.findElement(s) so
// much
public Abstract class PageBase {
private WebDriver driver = WebDriverFactory.getDriver();
// using var args to let you easily chain locators
protected By getBy(By... locator) {
return new ByChained(locator);
}
protected WebElement find(By... locators) {
return driver.findElement(getBy(locators));
}
protected List<WebElement> findEm(By... locators) {
return driver.findElements(getBy(locators));
}
protected Select select(By... locators) {
return new Select(getBy(locators));
}
}
public class somePage extends PageBase {
private static WebDriver driver = WebDriverFactory.getDriver();
private static final By buttonBy = By.cssSelector(".btn-primary");
public void clickButton() {
WebDriverWait wait = new WebDriverWait(driver, 10);
wait.until(ExpectedConditions.elementToBeClickable(buttonBy));
find(buttonBy).click();
}
}
У меня есть класс, полный статических методов WebDriverWait, которые я использую. И я не помню, будет ли вышеупомянутое использование WebDriver wait обработать исключение StaleElement или нет. Если нет, вы можете использовать свободное время, как в ответе DjangoFan. Но принцип, который я показываю, будет работать (даже если эта конкретная строка с WebDriverWait взорвется.
Итак, TL;DR;
- Используйте локаторы и комбинацию WebDriverWait/Fluent, которые ожидают/перемещают элемент самостоятельно, поэтому, если ваш элемент устарел, вы можете перенести его без дублирования локатора в
@FindBy
(для инициализированного элемента pagefactory) поскольку нет метода WebElement.relocate().
- Чтобы упростить жизнь, используйте абстрактный класс BasePage с удобными методами для определения элемента/списка элементов.
Ответ 21
Я пробовал многие из вышеупомянутых предложений, но простейший из них работал. В моем случае использование @CachelookUp для веб-элемента вызвало исключение устаревшего элемента. Я думаю, после обновления страницы ссылка на элемент не перезагрузилась и не удалось найти элемент. Отключение линии @CachelookUp для обработанного элемента.
//Search button
@FindBy(how=How.XPATH, using =".//input[@value='Search']")
//@CachelookUp
WebElement BtnSearch;
Ответ 22
Просто глупый вопрос, является ли всплывающее диалоговое окно нормальным окном предупреждения со входом?
Если это так, все, что вам нужно сделать, это driver.switchTo(). Alert