Подтвердить что ты не робот

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)
4b9b3361

Ответ 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
  1. Использовать крючки в используемых библиотеках 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)
  1. Перемещение ваших действий в JavaScript-инъекцию
 self.driver.execute_script("$(\"li:contains('Narendra')\").click()");
  1. Проактивно ждать, пока элемент станет устаревшим
  # 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