SeleniumHQ / selenium

A browser automation framework and ecosystem.
https://selenium.dev
Apache License 2.0
30.26k stars 8.15k forks source link

[🐛 Bug]: TypeScript types for driver.wait return single item instead of Array #14239

Open slhck opened 2 months ago

slhck commented 2 months ago

What happened?

I am using:

The TypeScript definition for webdriver.wait seems to be wrong or inconsistent.

In the below code snippet, I get an error:

Property 'length' does not exist on type 'WebElement'.ts(2339)

Yet, result is an array of WebElements:

> result.at(0)
WebElement {driver_: Driver, id_: Promise, log_: Logger}

Since driver.wait returns a WebElementPromise, it assumes it's only a single item, whereas the until.elementsLocated returns a Condition<WebElement[]>.

How can we reproduce the issue?

import webdriver, { By, until } from "selenium-webdriver";
import chrome from "selenium-webdriver/chrome.js";

const chromeOptions = new chrome.Options().addArguments(
  "--mute-audio",
  "--autoplay-policy=no-user-gesture-required"
);

const driver = new webdriver.Builder()
  .forBrowser("chrome")
  .setChromeOptions(chromeOptions)
  .build();

await driver.get("https://www.youtube.com");

const primaryBtnsPromise = driver.wait(
  until.elementsLocated(By.css("tp-yt-paper-button.style-primary"))
);
const rejectButtonPromise = driver.wait(
  until.elementsLocated(By.css('button[aria-label*="Reject"]'))
);

const result = await Promise.race([primaryBtnsPromise, rejectButtonPromise]);

console.log("result", Array.isArray(result)); // --> true!

if (result.length <= 0) {
  throw new Error(`No buttons found!`);
}

Relevant log output

See above

Operating System

N/A

Selenium version

4.22.0

What are the browser(s) and version(s) where you see this issue?

N/A

What are the browser driver(s) and version(s) where you see this issue?

N/A

Are you using Selenium Grid?

No

github-actions[bot] commented 2 months ago

@slhck, thank you for creating this issue. We will troubleshoot it as soon as we can.


Info for maintainers

Triage this issue by using labels.

If information is missing, add a helpful comment and then I-issue-template label.

If the issue is a question, add the I-question label.

If the issue is valid but there is no time to troubleshoot it, consider adding the help wanted label.

If the issue requires changes or fixes from an external project (e.g., ChromeDriver, GeckoDriver, MSEdgeDriver, W3C), add the applicable G-* label, and it will provide the correct link and auto-close the issue.

After troubleshooting the issue, please add the R-awaiting answer label.

Thank you!

github-actions[bot] commented 2 months ago

This issue is looking for contributors.

Please comment below or reach out to us through our IRC/Slack/Matrix channels if you are interested.

slhck commented 2 months ago

If I knew which change caused that regression I could have a deeper look, but the types and inner workings are bit too complex for me to grasp as an outsider.

marlapativ commented 2 months ago

@slhck One workaround is to force the constraints to use WebElement[]

const primaryBtnsPromise = driver.wait<WebElement[]>(
  until.elementsLocated(By.css("tp-yt-paper-button.style-primary"))
);
const rejectButtonPromise = driver.wait<WebElement[]>(
  until.elementsLocated(By.css('button[aria-label*="Reject"]'))
);

From what I understand, the issue shows up because typescript cannot differentiate between the following for wait function

  1. WebElementCondition (Condition<WebElement>)
  2. Condition<WebElement[]>
slhck commented 2 months ago

Yes, that might work. The thing is, this seems to be a regression. I have only encountered this error after upgrading from an older version of the types/selenium.

marlapativ commented 2 months ago

@diemol, Can I pick this issue?

Also if I understand correctly, I'd need to do changes in the types DefinitelyTyped repo, Correct?

harsha509 commented 1 month ago

Hi @marlapativ,

Yes, changing it there is correct.

I've looked into the types, and it seems new types have been merged recently. However, using generics is still the best approach for now. as returning Promise<WebElement | WebElement[]> from wait requires type guarding in code Array.isArray(result)

For WebElements, you should use driver.wait<WebElement[]> as it automatically infers the correct type for WebElement.

Thanks, Sri

slhck commented 1 month ago

Is there any indication as to which changes introduced this regression? I am a bit lost in the number of files and commits.

But yes, the return value of driver.wait should be generically inferred from the thing it's waiting for, as until.elementsLocated returns an array, always.