Selenium: WebElementCondition not instanceof Condition

Created on 2 Mar 2018  路  22Comments  路  Source: SeleniumHQ/selenium

Meta -

OS:
OSX 10.13.3

Selenium Version:
[email protected]

Browser:
Chrome

Browser Version:
Version 64.0.3282.186 (Official Build) (64-bit)

Expected Behavior -

driver.wait() should accept a WebElementCondition, i.e. driver.wait(until.elementLocated(By.css(selector)))

Actual Behavior -

the above example throws the error TypeError: Wait condition must be a promise-like object, function, or a Condition object

Steps to reproduce -

driver.wait(until.elementLocated(By.css(selector)))

This is some serious JavaScript weirdness:
Object.getPrototypeOf(condition) === WebElementCondition {}
yet condition instanceof WebElementCondition === false
weird, right?

I was able to get the case to pass by adding condition.constructor.name === 'WebElementCondition' to if (condition instanceof Condition) in the WebDriver's wait function.

C-nodejs

Most helpful comment

Hey guys, when can I expect to see this change live in the npm package? I noticed the published version was 7 months ago

All 22 comments

Huh...I can't reproduce this. Any chance you're using selenium-webdriver along with another package that might add some version skew?

What's the best way to verify this? I don't see any sign of another version in my dependency lock file.

I'm also getting this error. I just started a node project, so my package.json is still small (I've tried with node 8.10.0 too):

{
  "engines" : {
    "node" : "9.8.0"
  },
  "devDependencies": {
    "babel-jest": "^22.4.1",
    "babel-preset-env": "^1.6.1",
    "jest": "^22.4.2",
    "prettier": "^1.11.1",
    "selenium-webdriver": "^4.0.0-alpha.1"
  },
  "babel": {
    "presets": [
      "env"
    ]
  },
  "jest": {
    "transform": {
      "^.+\\.jsx?$": "babel-jest"
    }
  }
}

My test is doing this at the time of the error:

  // ...
  await webdriver.get(BASE_URL);
  await webdriver.wait(until.elementLocated(By.css('body')));
  // ...

Are you transpiling with babel? I wonder if that's mangling the class hierarchy

@jleyba, yes, I am transpiling, and I was wondering if that's causing the problem too. However, the line the error was coming from (node_modules/selenium-webdriver/lib/webdriver.js:809:13 in selenium-webdriver 4.0.0-alpha.1) is testing that the condition passed in (i.e. the return value of until.elementLocated(By.css('body')) in my code) has an fn property. It's hard to imagine that babel would be eliminating a property on an object, but I could be wrong.

By the way, I worked-around the problem by passing in my own async function to the wait function instead of the WebElementCondition returned by until.elementLocated(By.css('body')):

await webdriver.wait(async driver => await driver.findElement(By.css('body')));

@Tekhne additional context; since the WebElementCondition isn't being recognized as an instanceof Condition, fn doesn't get assigned a function, fails the next block, and throws the error you're seeing

@forrest-rival oh okay. I think I see that now. Thanks. I guess that points back towards Babel, I guess?

I guess we don't need to be so strict and could change this:

    let fn = /** @type {!Function} */(condition);
    if (condition instanceof Condition) {
      message = message || condition.description();
      fn = condition.fn;
    }

to something like

let fn = condition;
if (condition
    && typeof condition.description === 'function'
    && typeof condition.fn === 'function') {
  message = message || (condition.description() + '')
  fn = condition.fn;
}

There's a second instanceof check that probably isn't working either (but isn't an issue if you await the result of wait()):

https://github.com/SeleniumHQ/selenium/blob/master/javascript/node/selenium-webdriver/lib/webdriver.js#L845

@jleyba instead of changing the code in wait(), maybe consider adding a custom hasInstance static method to the Condition and WebCondition classes.

static [Symbol.hasInstance](condition) {
  return condition
    && typeof condition.description === 'string'
    && typeof condition.fn === 'function';
}

instanceof will then use this static method to compare an instance to that class and no changes would be necessary in wait()

@jleyba bumping since you probably don't need the R-awaiting answer label anymore

I'm seeing the same behavior with different weirdness. I see it depending on whether one of my require calls is relative or absolute, i.e. require('./dir/file.js') gives the wait condition error but require('c:/path/to/dir/file.js') works fine.

I started with webdriver 4.0.0-alpha.1, then downgraded to 3.6.0 and still see the problem.

I'm running on windows with firefox and node version 8.11.1.

If it would help, I could zip up my files that are seeing the issue and upload those.

@forrest-rival are you planning on creating a PR if @jleyba is okay with your proposal? I'm happy to help with this issue if needed.

@afsmith92 I wasn't planning on it, but gladly will if it helps. I believe the snippet I posted is all that's required here.

@forrest-rival I went ahead and created a PR. Thank you for digging into this and finding a fix.

Hey guys, when can I expect to see this change live in the npm package? I noticed the published version was 7 months ago

In my case, the issue seemed to be caused by npm bringing in two versions of selenium-webdriver, one directly under node_modules/ and one under node_modules/selenium-cucumber-js/node_modules. The ugly workaround for now is to have a postinstall that removes the one under selenium-cucumber-js.

If anyone stumbled on the issue, my colleague came up a workaround.
You need to define a condition and then pass an async function in driver.wait. Take an example as follows:

const condition = until.elementLocated(By.name('loader'))
driver.wait(async driver => condition.fn(driver), 10000, 'Loading failed.')

Hope this helps.

If anyone stumbled on the issue, my colleague came up a workaround.
You need to define a condition and then pass an async function in driver.wait. Take an example as follows:

const condition = until.elementLocated(By.name('loader'))
driver.wait(async driver => condition.fn(driver), 10000, 'Loading failed.')

Hope this helps.

Thanks! This helped a lot.

When I run my scrape locally, everything works. When I upload it as a package to an AWS Lambda function, with the same version (4.0.0 alpha), it produces this error. Any idea why locally it would work fine, but on lamdba, it will trigger this?

I've reverted the change proposed in PR #5968 because it broke x instanceof WebElementCondition check that returns true even if x is an instance of the Condition class (a base class for WebElementCondition). All waits that return a boolean value or a string are affected.

Looking for a better solution...

Do we have any plan on what the next steps are now the change has been backed out?

This feels like a pretty critical thing to fix -- I'm happy to take a stab at putting something together if we can agree an approach

I guess this is explaining the problem: https://github.com/nodejs/node/issues/13408

This maybe is triggered when the npm index.js is importing both until and webdriver, while until is requiring webdriver simultaneously? Just a guess.

Was this page helpful?
0 / 5 - 0 ratings