Я пытаюсь написать объект Selenium Builder для Swagger на странице . Для целей этого обсуждения мой код проблемы можно свести к следующему:

class SwaggerBuilder {

    WebDriver driver
    def resources

    SwaggerBuilder(WebDriver driver) {

        this.driver = driver

        Thread.sleep(2000)

        resources = driver.findElements(By.className("resource")).collectEntries {
            def resourceName = it.findElement(By.tagName("a")).getText().replaceFirst("[/]", "")
            [(resourceName): it]
        }
    }

    Object invokeMethod(String name, Object args) {

        if(resources[(name)] == null)
            throw new NoSuchElementException("Resource $name cannot be found.")

        resources[(name)].findElement(By.linkText("List Operations")).click()
    }
}

Вызвать это очень просто (из JUnit3):

void test1() {

    driver = new FirefoxDriver()
    driver.get("http://petstore.swagger.wordnik.com/")

    def petstore = new SwaggerBuilder(driver)    // problem does not get past this line!
    try {
        petstore.not_there()
        fail("Did not catch exception.")
    } catch(NoSuchElementException ex) {
        assertTrue(ex.message.startsWith("Resource not_there cannot be found."))
    } catch(Exception ex) {
        fail("Caught wrong exception: ${ex.class}.")
    }
}

Thread.sleep(2000) в конструкторе ужасно раздражает! Я попытался заменить его следующими ожиданиями:

def wait = new WebDriverWait(driver, 20)
wait.until(ExpectedConditions.visibilityOfElementLocated(By.className("resource")))

Или же:

    def wait = new FluentWait<By>(By.className("resource")).
            withTimeout(20, TimeUnit.SECONDS).
            pollingEvery(100, TimeUnit.MILLISECONDS).
            ignoring(StaleElementReferenceException)
    wait.until(new Function<By, Boolean>() {
                def count = 0
                @Override
                Boolean apply(By by) {
                    def oldCount = count
                    count = driver.findElements(by).size()
                    return count == oldCount
                }
            })

Оба они дали одинаковый результат: «org.openqa.selenium.StaleElementReferenceException: элемент больше не прикреплен к DOM» в закрытии строки, которая начинается с def resourceName = ....

Thread.sleep(2000) - это единственный способ, которым я могу заставить эту работу работать прямо сейчас. Я надеюсь заменить его более удобным для браузера / надежным ожиданием, чтобы это могло работать даже для страниц, которые загружаются медленнее, чем 2 секунды. Есть другие идеи?

-1
SiKing 23 Окт 2014 в 21:59

2 ответа

Лучший ответ

Когда я загружаю вашу страницу, я вижу в консоли трижды "Loaded SwaggerUI". Это твоя проблема: SwaggerUI загружается 3 раза больше.

Как это понять

Итак, я сделал это:

  1. Я помещаю точку останова на строку, которая выводит "Loaded SwaggerUI".

  2. Я перезагрузился.

  3. Когда я достиг точки останова, я сделал снимок элементов, имеющих класс resource:

    var snap1 = Array.prototype.slice.call(
                    document.getElementsByClassName("resource"))
    

    (Вы должны скопировать возвращаемое значение в Arrayslice здесь), потому что getElementsByClassName возвращает действующую коллекцию.)

  4. Я нажимаю кнопку продолжения отладчика.

  5. Когда я снова попал в точку останова, я сделал второй снимок (названный snap2).

Хорошо, теперь несколько тестов. Если DOM не изменился, элементы должны быть идентичными:

> snap1[0] === snap2[0]
false

Это не выглядит хорошо. Посмотрим, что еще находится в дереве DOM:

> document.contains(snap1[0])
false
> document.contains(snap2[0])
true

Элемента в первом снимке больше нет в дереве, но есть элемент во втором.

Почему ошибка Selenium?

Двухсекундного ожидания достаточно, чтобы позволить Selenium начать поиск элементов после стабилизации DOM. Однако, когда вы говорите Selenium подождать, пока на странице не появятся видимые элементы класса resource, он ждет, пока SwaggerUI загрузится в первый раз . В какой-то момент, когда он обрабатывает элементы, которые он впервые находит, SwaggerUI загружает другой раз , а затем старые найденные элементы больше не находятся в дереве DOM. Таким образом, он вызывает StaleElementReferenceException, потому что элемента, который он однажды нашел в дереве DOM, больше нет. Он был заменен элементом, который находится в том же месте и структурно идентичен, но Selenium хочет, чтобы точно такой же элемент не являлся идентичной копией.

2
Louis 23 Окт 2014 в 22:40

После самого отличного исследования / предложения @Louis я в конечном итоге использовал:

def wait = new FluentWait<By>(By.className("resource")).
        withTimeout(10, TimeUnit.SECONDS).
        ignoring(NoSuchElementException)
wait.until(new Function<By, Boolean>() {
            WebElement res
            Boolean apply(By by) {
                def oldRes = res
                res = driver.findElement(by)
                return res == oldRes
            }
        })

Если кому-то интересно, весь Builder можно найти на SourceForge (на момент написания статьи он все еще находится в разработке).

1
SiKing 24 Окт 2014 в 20:39