60

I have searched on Google and the SO site and I get answers for JAVA but do not seem to get answers for node.js

I have a web app that takes time to load. I would like the selenium program to wait till the page is loaded and then perform some actions.

My current code is as follows

//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();

The error I get is:

    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)
Mario Petrovic
  • 7,500
  • 14
  • 42
  • 62
Anand Sunderraman
  • 7,900
  • 31
  • 90
  • 150
  • Is your site handling ajax requests? If so have you thought of using javascriptexecutor class in java to check node.js flags for the request? – Fahim Hossain Sep 10 '14 at 01:23

9 Answers9

59

I stumbled upon an answer to my question

So to wait for an element to appear we have to:

driver.wait(function () {
    return driver.isElementPresent(webdriver.By.name("username"));
}, timeout);
sdude
  • 7,985
  • 2
  • 21
  • 24
Anand Sunderraman
  • 7,900
  • 31
  • 90
  • 150
45

You don't need a custom function, you can just do this:

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

See the docs here.

Willi Mentzel
  • 27,862
  • 20
  • 113
  • 121
Alexander Mills
  • 90,741
  • 139
  • 482
  • 817
  • 1
    This is an up to date and idiomatic solution that works. Should be voted much higher! – jaredsk Mar 05 '18 at 19:29
  • 1
    Doesn't work if element is not in the DOM. – Бранко Пејић Aug 13 '22 at 15:44
  • @БранкоПејић I assume it throws an error if el is not found? you tell us bro – Alexander Mills Aug 13 '22 at 17:27
  • @AlexanderMills Yes it will throw a `TimeoutError` on the first line of code if the element does not exist in the DOM at the expiration of WebDriver's implicit timeout. If the element is found before the timeout, the next line of code will then wait an additional (explicit) 100 milliseconds for the element to become visible. You could instead have a single, explicit timeout on the visibility predicate by passing the return value of `until.elementIsVisible(...)` to `driver.wait()` similar to how [this answer](https://stackoverflow.com/a/29156163/159570) does. – Joe Coder Aug 28 '22 at 04:23
44

You can register a listener on webdriver.wait by using then()

driver.wait(until.elementLocated(By.name('username')), 5 * 1000).then(el => {
    el.sendKeys(username);
});
Joe Coder
  • 4,498
  • 31
  • 41
  • Could you explain why you have `5 * 1000` vs `5000`? (btw, I'm not the downvoter... :) ) – jibbs Nov 15 '18 at 15:30
  • 17
    This is a common pattern when dealing with milliseconds. Many devs prefer the clarity indicating that the measurement is in milliseconds. – Jonny Asmar Dec 03 '18 at 00:28
  • 2
    This one worked for me, remember to const {until} = require('selenium-webdriver'); before – Fernando Gabrieli Jul 08 '20 at 10:34
  • Note: I wanted to click on a button when it is displayed but to make this solution work, I had to use the `sleep` function inside the `then` method before clicking on it, for example by waiting 1 second after it is displayed: `driver.sleep(1000).then(function() {el.click();});`. You will also have less chance to be detected as a bot. – baptx Apr 22 '21 at 14:30
  • @baptx Ideally you want the automation to run as fast as possible without explicit waits. You could try `until.elementIsVisible()` instead or call `driver.wait()` and pass in a custom function. See [docs](https://www.selenium.dev/selenium/docs/api/javascript/module/selenium-webdriver/lib/until.html). Avoiding bot detection in Selenium is quite a bit more involved, as you also have to change the User Agent and other settings. – Joe Coder Apr 23 '21 at 03:53
  • @JoeCoder thanks, replacing elementLocated code with `driver.wait(until.elementIsVisible(driver.findElement(By.name("btnK"))))` worked for Google Search button. Mozilla definitely needs to update examples https://developer.mozilla.org/en-US/docs/Learn/Tools_and_testing/Cross_browser_testing/Your_own_automation_environment#Setting_up_Selenium_in_Node and https://developer.mozilla.org/en-US/docs/Mozilla/Firefox/Headless_mode. However it does not seem to work on some buttons: https://stackoverflow.com/questions/67230781/selenium-webdriver-function-elementisvisible-does-not-work-on-all-buttons – baptx Apr 23 '21 at 13:29
5

This is the only thing that is working for me:

const element = By.id('element');
driver.wait(until.elementLocated(element));
const whatElement = driver.findElement(element);
driver.wait(until.elementIsVisible(whatElement), 5000).click();
Christopher Grigg
  • 2,238
  • 27
  • 33
3

Try something like this:

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);
            }
        });
}

I adapted it slightly based on what I found here: Check if element exists - selenium / javascript / node-js and it worked a charm.

Community
  • 1
  • 1
QualiT
  • 1,934
  • 2
  • 18
  • 37
3

I came up with this approach because it maintains the chainable promise syntax so that I can write this: await waitFind(By.id('abc')).click()

const waitFind = (locator) => {
    return driver.findElement(async () => {
        await driver.wait(until.elementLocated(locator));
        return driver.findElement(locator);
    });
}
James H
  • 63
  • 1
  • 5
  • Can you be more precise? How does this correct the error encountered in the question? – Alexandre Fenyo Aug 05 '18 at 03:19
  • @Alexandre The answer to the question is to wait for the element to appear. The other answers propose various solutions to do this, but this one has the benefit of retaining the proxy element returned from findElement. Otherwise, the returned object would be a promise lacking the click() and sendKeys() proxy methods. – James H Aug 06 '18 at 00:45
2

I usually use this way:

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

Writing asynchronous function to avoid this problem

(async function() {
  let url = args[0];
  await driver.get(url);
  driver.quit();
})();
Tamil
  • 21
  • 3
0

The main problem is webdriver thinks element is already there, but not yet. I have a solution, ugly but works. After the webdriver think item is there, try to click on. Get an error message:

StaleElementReferenceError: stale element reference: element is not attached to the page document  (Session info: chrome=83.0.4103.106)

No problem, in the loop waiting 500ms, and try to click on again. In my case 5 try is enough, about 2-3 click is success.

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" );