Selenium WebDriver等待元素显示

60

我在谷歌和SO网站上进行了搜索,我得到了有关JAVA的答案,但似乎没有得到有关node.js的答案。

我有一个加载时间较长的Web应用程序。我希望selenium程序等待页面加载完成后再执行一些操作。

我的当前代码如下:

//dependencies
var webdriver = require('selenium-webdriver'),
    util = require('util'),
    _ = require('underscore');

var driver = new webdriver.Builder().withCapabilities(webdriver.Capabilities.chrome()).build();
var branchName =  _.isUndefined(process.argv[3]) ? 'branch' : process.argv[3], 
    hostName = _.isUndefined(process.argv[2]) ? 'localhost' : process.argv[2],
    appTmpl = 'http://%s/%s',
    username = 'xxxx',
    password = 'xxxx';
var appUrl = util.format(appTmpl, hostName, branchName);

driver.get(appUrl);
driver.findElement(webdriver.By.name("username")).sendKeys(username);
driver.findElement(webdriver.By.name("password")).sendKeys(password);
driver.findElement(webdriver.By.name("login_button")).click();
driver.quit();

我收到的错误信息是:

    C:\Work\study\selenium\node_modules\selenium-webdriver\lib\webdriver\promise.js:1643
      throw error;
            ^
NoSuchElementError: no such element
  (Session info: chrome=37.0.2062.103)
  (Driver info: chromedriver=2.10.267521,platform=Windows NT 6.1 SP1 x86_64)
    at new bot.Error (C:\Work\study\selenium\node_modules\selenium-webdriver\lib\atoms\error.js:109:18)
    at Object.bot.response.checkResponse (C:\Work\study\selenium\node_modules\selenium-webdriver\lib\atoms\response.js:106:9)
    at C:\Work\study\selenium\node_modules\selenium-webdriver\lib\webdriver\webdriver.js:277:20
    at C:\Work\study\selenium\node_modules\selenium-webdriver\lib\goog\base.js:1243:15
    at webdriver.promise.ControlFlow.runInNewFrame_ (C:\Work\study\selenium\node_modules\selenium-webdriver\lib\webdriver\promise.js:1539:20)
    at notify (C:\Work\study\selenium\node_modules\selenium-webdriver\lib\webdriver\promise.js:362:12)
    at notifyAll (C:\Work\study\selenium\node_modules\selenium-webdriver\lib\webdriver\promise.js:331:7)
    at resolve (C:\Work\study\selenium\node_modules\selenium-webdriver\lib\webdriver\promise.js:309:7)
    at fulfill (C:\Work\study\selenium\node_modules\selenium-webdriver\lib\webdriver\promise.js:429:5)
    at C:\Work\study\selenium\node_modules\selenium-webdriver\lib\webdriver\promise.js:1406:10
==== async task ====
WebDriver.findElement(By.name("username"))
    at webdriver.WebDriver.schedule (C:\Work\study\selenium\node_modules\selenium-webdriver\lib\webdriver\webdriver.js:268:15)
    at webdriver.WebDriver.findElement (C:\Work\study\selenium\node_modules\selenium-webdriver\lib\webdriver\webdriver.js:711:17)
    at Object.<anonymous> (C:\Work\study\selenium\test.js:15:8)
    at Module._compile (module.js:456:26)
    at Object.Module._extensions..js (module.js:474:10)
    at Module.load (module.js:356:32)
    at Function.Module._load (module.js:312:12)
    at Function.Module.runMain (module.js:497:10)
    at startup (node.js:119:16)

你的网站是否处理ajax请求?如果是,你有没有考虑使用Java中的JavascriptExecutor类来检查请求的node.js标志? - Fahim Hossain
9个回答

59

我偶然发现了答案解决了我的问题

所以,要等待元素出现,我们必须:

driver.wait(function () {
    return driver.isElementPresent(webdriver.By.name("username"));
}, timeout);

感谢您的回答。 - KodingKid
32
“isElementPresent”函数在3.0中已被弃用,因此在更新版本中将无法使用。 - jmreicha
4
@jmreicha,你能否解释一下在较新的版本中有哪些功能是有效的? - Anand Sunderraman
3
使用 elementLocated 函数的下面的答案对我有用。 - jmreicha
@jmreicha 在我的情况下,当 spinner 正在加载并尝试点击按钮时,elementLocated 被拦截了! - Vignesh

45

你不需要自定义函数,只需这样做:

let el = await driver.findElement(By.id(`import-file-acqId:${acqId}`));
await driver.wait(until.elementIsVisible(el),100);
await el.sendKeys(file);

请查看这里的文档。


1
这是一个最新的、通俗易懂的解决方案,非常有效。应该得到更高的评价! - jaredsk
2
如果元素不在DOM中,则无法工作。 - Бранко Пејић
@БранкоПејић 我猜如果找不到el,它会抛出一个错误?你告诉我们吧兄弟。 - Alexander Mills
@AlexanderMills 如果在 WebDriver 的隐式超时期间,元素不存在于 DOM 中,则它将在第一行代码中引发 TimeoutError。如果在超时之前找到该元素,则下一行代码将等待额外的(显式)100 毫秒以使元素可见。您可以通过将 until.elementIsVisible(...) 的返回值传递给 driver.wait() 来对可见性谓词进行单个显式超时,类似于此答案 所做的方式。 - Joe Coder

44

你可以使用then()webdriver.wait上注册监听器。

driver.wait(until.elementLocated(By.name('username')), 5 * 1000).then(el => {
    el.sendKeys(username);
});

你能解释一下为什么你用了 5 * 1000 而不是 5000 吗?(顺便说一句,我不是那个给你点踩的人... :)) - jibbs
17
处理毫秒时,这是一个常见的模式。许多开发人员喜欢明确指出测量单位为毫秒的清晰度。 - Jonny Asmar
2
这个对我有用,记得在之前加上 const {until} = require('selenium-webdriver'); - Fernando Gabrieli
注意:我想在按钮显示时单击它,但为了使此解决方案起作用,我必须在单击之前在then方法中使用sleep函数,例如在其显示后等待1秒钟:driver.sleep(1000).then(function() {el.click();});。这样做还可以降低被检测为机器人的几率。 - baptx
@baptx 理想情况下,您希望自动化运行尽可能快,而不需要显式等待。您可以尝试使用 until.elementIsVisible() 或调用 driver.wait() 并传入自定义函数。请参见文档。在Selenium中避免机器人检测要复杂得多,因为您还必须更改用户代理和其他设置。 - Joe Coder
@JoeCoder 谢谢,用 driver.wait(until.elementIsVisible(driver.findElement(By.name("btnK")))) 替换 elementLocated 代码对于 Google 搜索按钮起作用了。Mozilla 明显需要更新示例 https://developer.mozilla.org/en-US/docs/Learn/Tools_and_testing/Cross_browser_testing/Your_own_automation_environment#Setting_up_Selenium_in_Node 和 https://developer.mozilla.org/en-US/docs/Mozilla/Firefox/Headless_mode。然而,它似乎不能在某些按钮上工作:https://stackoverflow.com/questions/67230781/selenium-webdriver-function-elementisvisible-does-not-work-on-all-buttons - baptx

5

这是目前对我有效的唯一方法:

const element = By.id('element');
driver.wait(until.elementLocated(element));
const whatElement = driver.findElement(element);
driver.wait(until.elementIsVisible(whatElement), 5000).click();

3
我提出了这种方法,因为它保持了可链式调用的 Promise 语法,这样我就可以写成:await waitFind(By.id('abc')).click()
const waitFind = (locator) => {
    return driver.findElement(async () => {
        await driver.wait(until.elementLocated(locator));
        return driver.findElement(locator);
    });
}

你可以更具体一些吗?这句话如何纠正遇到的问题? - Alexandre Fenyo
@Alexandre,这个问题的答案是等待元素出现。其他答案提出了各种解决方案,但这个答案有一个好处,就是保留了从findElement返回的代理元素。否则,返回的对象将是一个缺少click()和sendKeys()代理方法的promise。 - James H

3

尝试像这样:

function isItThere(driver, element){

    driver.findElement(webdriver.By.id(element)).then(function(webElement) {
            console.log(element + ' exists');
        }, function(err) {
            if (err.state && err.state === 'no such element') {
                console.log(element + ' not found');
            } else {
                webdriver.promise.rejected(err);
            }
        });
}

我根据这里找到的内容稍作修改:检查元素是否存在 - selenium / javascript / node-js,结果非常好。


2

我通常使用以下方式:

 var el = driver.wait(until.elementLocated(By.name('username')));
el.click();

1
编写异步函数以避免此问题。
(async function() {
  let url = args[0];
  await driver.get(url);
  driver.quit();
})();

0
主要问题是webdriver认为元素已经存在,但实际上还没有。我有一个解决方案,虽然不太美观但有效。在webdriver认为元素已经存在后,尝试点击它。会收到一个错误消息:
StaleElementReferenceError: stale element reference: element is not attached to the page document  (Session info: chrome=83.0.4103.106)

没问题,在循环中等待500毫秒,然后再尝试点击。在我的情况下,尝试5次就足够了,大约需要2-3次点击才能成功。
async clickonitem( driver, itemname ) {
    const strftime = require('strftime');
    var trycounter = 0;
    var timeout = 500;
    var success;
    do {
      try {
        trycounter++;
        success = true;
        console.log( strftime('%F %T.%L'), "Finding #" + trycounter + " " + itemname );
        var item = await driver.wait( until.elementLocated( By.xpath( '//input[@name="' + itemname +'"]' ) ), 
                        timeout );
        console.log( strftime('%F %T.%L'), "Found or Timeout #" + trycounter );
        //await item.click();
        await driver.wait( item.click(), 
                        timeout );
        console.log( strftime('%F %T.%L'), "Click #" + trycounter + " " + itemname );
      } 
      catch(err) {
        success = false;
        //this.log( "Error #" + trycounter + " " + itemname + "\n" +err );
        this.log( strftime('%F %T.%L'), "Error #" + trycounter + " " + itemname + " waiting: " + timeout );
        await wait( timeout );
        continue;
      }

    } while( !success && trycounter < 5 );
}       
        async wait( ms ) {
          return new Promise((resolve) => {
            setTimeout(resolve, ms);
          });
        }

clickonitem( driver, "login_button" );

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接