【问题标题】:Selenium WebDriverJS thenCatch not catching StaleElementExceptionSelenium WebDriverJS thenCatch 没有捕获 StaleElementException
【发布时间】:2015-09-29 09:03:56
【问题描述】:

我正在运行 node.js 和 Selenium WebDriverJS。我的一项测试因以下错误而失败:

UnknownError: unknown error: Runtime.evaluate threw exception: Error: element is not attached to the page document

我知道这本质上是一个 StaleElementReferenceException,但我还没有找到可靠的解决方法。我尝试了以下方法但没有成功:

  1. 等待该元素出现在页面上,然后再查找并点击该元素

    waitForElement: function (selector, timeout) {
        if (typeof(timeout) === 'undefined') { timeout = 3000; }
        driver.wait(function() {
            return driver.findElements(selector).then(function(list) {
                return list.length > 0;
            });
        }, timeout);
    }
    
  2. 在找到并点击元素之前等待一段明确的时间 (driver.sleep(1000))
  3. 在点击元素之前多次查找元素(使用.findElement()
  4. 使用承诺链来捕获任何错误并尝试重新单击元素

    driver.getTitle().then(function(title) {
        driver.findElement(webdriver.By.xpath(...)).click();
    }).thenCatch(function(e) {
        driver.findElement(webdriver.By.xpath(...)).click();
    });
    
  5. 使用带有递归函数的承诺链来不断尝试重新点击元素

    var getStaleElement = function(selector, callback) {
        var element = driver.findElement(selector);
        callback(element);
    }).thenCatch(function(e) {
        getStaleElement(selector, callback);
    });
    
    var clickSelf = function(ele) { return ele.click() };
    
    driver.getTitle().then(function(title) {
        driver.findElement(webdriver.By.xpath(...)).click();
    }).thenCatch(function(e) {
        getStaleElement(webdriver.By.xpath(...), clickSelf);
    });
    
  6. 方法 4 和 5 使用 .then() 的 errback 参数代替 .thenCatch()
  7. 以上组合

Selenium 似乎无法捕获此特定错误。我使用 print 语句来确认 NoSuchElementError 等其他错误被 .thenCatch() 捕获。是否有一种解决方法可以让我处理陈旧的元素?

【问题讨论】:

  • 我忘了在原帖中提到这个错误不是每次运行测试都会出现,而是偶尔出现。
  • 你有没有找到解决这个问题的方法?

标签: javascript selenium selenium-webdriver webdriver


【解决方案1】:

我遇到了类似的问题,所以我做了下面的解决方法,你可以试试...

/*
 * params.config - {
 *           opposite - {Boolean} - if true, will wait till negative result is reached/ error is thrown.
 *           maxWaitTime - {Number} - if this time exceeds, just throw an error and leave.
 *           waitTime - {Number} - wait time between two checks.
 *           expectValue - {Boolean} - where you just want it to run without error, or it should expect a value
 *           expectedValue - {Object} - object value it should or should not match.
 *         }
 *  params.fn - a function that returns a promise that we want to keep checking till desire value is reached
 */
function waiter(fn, config){
    config = config || {};
    var deffered = Driver.promise.defer(),  
        wt = config.waitTime || 100,
        mwt = config.maxWaitTime || 3000,
        timeoutReached = false,
        pCall = function(){
                        fn().then(pThen, pCatch);
                    },
        pThen = function(data){
                    if(timeoutReached)  return;
                    if(config.expectValue){
                        if(config.opposite){                        
                            if(data == config.expectedValue){
                                setTimeout(pCall, wt);
                            }else{
                                clearTimeout(vTimeout);
                                deffered.fulfill(true);
                            }
                        }else{
                            if(data == config.expectedValue){
                                clearTimeout(vTimeout);
                                deffered.fulfill(true);
                            }else{
                                setTimeout(pCall, wt);
                            }
                        }
                    }else{
                        deffered.fulfill(true);
                    }
                },  
        pCatch = function(err){
                    if(timeoutReached)  return;
                    if(config.opposite){
                        deffered.fulfill(true);
                    }else{
                        setTimeout(pCall, wt);
                    }
                };  

    pCall();    

    var vTimeout = setTimeout(function(){
        timeoutReached = true;
        if(config.opposite){
            deffered.fulfill(true);         
        }else{
            deffered.reject(new Error('timed-out'));
        }
    }, mwt);
    return deffered.promise;
}

示例用法(针对您的情况):

var myPromise = function(){
    return driver.findElement(webdriver.By.xpath(...)).click();
};

//default use
waiter(myPromise).then(function(){
    console.log('finally...');
}).catch(fucntion(err){
    console.log('not working: ', err);
});

// with custom timeout after 10 seconds
waiter(myPromise, {maxWaitTime: 10000}).then(function(){
    console.log('finally...');
}).catch(fucntion(err){
    console.log('not working: ', err);
});

【讨论】:

  • 我尝试实施您的解决方法,但同样的错误UnknownError: unknown error: Runtime.evaluate threw exception: Error: element is not attached to the page document 仍然出现。我将.catch(...) 更改为.thenCatch(...) 以编译,但console.log('not working: ', err); 没有记录任何内容。关于为什么会发生这种情况的任何想法?
  • @eytac,问题是,我不明白错误是如何传播的,因为我实现的方式,它会在发生错误时继续尝试,直到达到超时(唯一可能的错误必须是超时错误)...我不明白你是如何得到UnknownError: unknown error:
  • 我最初尝试编写一个递归函数,当错误发生时应该继续尝试(上面的方法 5),但似乎无论出于何种原因都没有捕获到这个错误,所以你的 errback 参数pCall() 函数似乎没有被调用。
  • 我刚刚在pCatch()函数的开头添加了一个console.log()语句,但没有记录任何内容,因此出现错误时不会调用pCatch()
  • 你可以试试var button = driver.wait(until.elementLocated(By.xxx(someSelector), 10000); button.click();
【解决方案2】:

您对getStaleElement 有正确的想法,但没有正确编码。这是可行的:

function retryOnStale(selector, callback) {
    return browser.findElement(selector).then(callback)
        .thenCatch(function (err) {
            if (err.name === 'StaleElementReferenceError')
                return retryOnStale(selector, callback);

            throw err;
        });
}

这是演示其用法的代码。您会发现retryOnStaleIllustrate 包含会导致发生过时元素异常的代码,并且它有一个console.log 用于诊断。然后是retryOnStale,和我上面展示的一样。

var webdriver = require('selenium-webdriver');
var chrome = require('selenium-webdriver/chrome');
var By = webdriver.By;
var until = webdriver.until;

var browser = new chrome.Driver();

browser.get("http://www.example.com");

function make_stale() {
    browser.executeScript("document.body.innerHTML = " +
                          "'<p id=\\'foo\\'>foo text</p>'");
}

// Create our initial p element with id `foo`.
make_stale();

var fake_stale = 10;

// We have to use var ... = ... because later in this code we are
// going to change the value of retryOnStaleIllustrate.
function retryOnStaleIllustrate(selector, callback) {
    return browser.findElement(selector).then(function (element) {

        //
        // This code is here to simulate a process that causes the element
        // we acquired to become stale.
        //
        if (fake_stale) {
            make_stale();
            fake_stale--;
        }

        return callback(element);
    }).thenCatch(function (err) {
        if (err.name === 'StaleElementReferenceError') {
            console.log("stale: retrying");
            return retryOnStaleIllustrate(selector, callback);
        }

        throw err;
    });
}

retryOnStaleIllustrate(By.id("foo"), function (element) {
    element.getText().then(console.log);
});

// Once we remove the code to simulate an element becoming stale, and
// the console.log for diagnosis, this is what we are left with:
function retryOnStale(selector, callback) {
    return browser.findElement(selector).then(callback)
        .thenCatch(function (err) {
            if (err.name === 'StaleElementReferenceError')
                return retryOnStale(selector, callback);

            throw err;
        });
}

// This just shows that retryOnStale returns a promise which can be used.
retryOnStale(By.id("foo"), function (element) {
    return element.getText();
}).then(function (text) {
    console.log(text);
});

browser.quit();

这个例子可以被执行并且应该产生这个输出:

stale: retrying
stale: retrying
stale: retrying
stale: retrying
stale: retrying
stale: retrying
stale: retrying
stale: retrying
stale: retrying
stale: retrying
foo text
foo text

所有stale: retrying 行和第一行foo text 均由使用retryOnStaleIllustrate 的代码生成。最后foo text 行由使用retryOnStale 的代码生成。

【讨论】:

  • 同样的错误UnknownError: unknown error: Runtime.evaluate threw exception: Error: element is not attached to the page document 仍然出现。我在retryOnStale().thenCatch() 块中放了一个console.log(),但出现错误时没有记录任何内容。出现错误时,console.log(text); 行也不会记录任何内容。似乎错误仍未被捕获。关于为什么会发生这种情况的任何想法?
  • 你能执行我在答案中给出的例子吗?我刚刚修改了代码,因为出于说明目的和retryOnStale 的最终版本使用相同的函数名称存在问题。我还添加了它应该产生的输出。
  • 当我运行你的代码时,我得到的结果和你一样。但是,当我尝试在测试中使用您的解决方案 (retryOnStale()) 时,抛出的错误不是StaleElementReferenceError,而是UnknownError,无论出于何种原因,它都没有被.thenCatch() 块捕获.
  • 我忘了在原帖中提到这个错误并不是每次运行测试都会出现,而是偶尔出现。也许这些信息在某些方面有用?
  • 问题是间歇性的这一事实并不意外。这通常是StaleElementReferenceError 发生的情况。我从未有过由 Selenium 生成的UnknownError。当我在您的问题中看到它时,我没有做太多,因为我认为也许其他东西可能会干扰异常传播。当您说UnknownError 未被.thenCatch() 捕获时,您的意思是说如果您将console.log(err); 放在传递给.thenCatch() 的函数的开头,那么您根本不会得到该错误的转储控制台?
猜你喜欢
  • 2015-04-22
  • 2017-11-07
  • 1970-01-01
  • 1970-01-01
  • 2017-10-14
  • 1970-01-01
  • 2023-04-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多