webdriverio / webdriverio

Next-gen browser and mobile automation test framework for Node.js
http://webdriver.io
MIT License
9.04k stars 2.5k forks source link

[💡 Feature]: Expose browser type when it is returned by remote in standalone mode #12545

Closed xiaoyurenqa closed 6 months ago

xiaoyurenqa commented 7 months ago

Is your feature request related to a problem?

Mode: Standalone mode

We use webdriverio in its standalone mode, using TypeScript, and it seems like there is no way to declare the browser's type since it's not exposed. We found a workaround to get the type by importing browser from global, like this:

import type { browser } from "@wdio/globals";

let someBrowser: typeof browser;

While this solution works, but it looks awkward and it is not able to automatically detect methods supported by the type (WebdriverIO.Browser).

Describe the solution you'd like.

It would be nice if webdriverio can expose the WebdriverIO.Browser type for import so that it can be properly declared in the project and can properly detect supported methods and properties when using webdriverio in standalone mode.

Describe alternatives you've considered.

Since the version we are using is 8.1.3, which is a bit behind, if there's already a solution to do this more properly, please let me know. I didn't find much discussions or docs out there for this issue.

Additional context

No response

Code of Conduct

naruaway commented 7 months ago

and it seems like there is no way to declare the browser's type since it's not exposed.

I think you can directly use WebdriverIO.Browser (playground link) but I think it is not able to automatically detect methods supported by the type (WebdriverIO.Browser). is not true since they are exactly the same type

import type { browser } from "@wdio/globals";

const someBrowser: typeof browser = {} as any;

someBrowser.findElement
// ^?

const otherBrowser: WebdriverIO.Browser = {} as any
       // ^?

otherBrowser.findElement

I personally do not like relying on TypeScript namespace but WebdriverIO namespace is used in many places already.

Note that unrelated to the original issue, but having importing @wdio/globals introduces several global variables in type level such as driver and expect but in runtime, there is no such variables. You can see this in the playground, where there is no type error for global driver variable but if you run the script, it fails since there is no driver. I am not sure whether it's good to use @wdio/globals in standalone mode

christian-bromann commented 7 months ago

You can import the WebdriverIO.Browser or WebdriverIO.Element type we define within the WebdriverIO namespace via:

import type { Browser, Element } from 'webdriverio'

Do you have a suggestion where we should document this better?

UPDATE: this is not a recommended way of using these types which is why they are deprecated.

naruaway commented 7 months ago

@christian-bromann Both Browser and Element types from webdriverio is marked as @deprecated (Playground, code) and it looks like this decision was done in https://github.com/webdriverio/webdriverio/pull/11590

IMHO, I think we should do the opposite. We can stop relying on TypeScript namespace and instead we can rely on TypeScript modules. TypeScript namespace is unnecessarily global. I know this is an edge case but TypeScript modules work better for any situations such as having different versions of webdriverio package via npm alias:

import type { Browser as Browser1, Element as Element1 } from 'webdriverio@8.1'
import type { Browser as Browser2, Element as Element 1} from 'webdriverio@8.2'

Just curious, is there strong reason to prefer TypeScript namespace? Is this due to historical reasons?

UPDATE: I just found the reason described in https://github.com/webdriverio/webdriverio/issues/11055#issuecomment-1698164693, we want to use TypeScript namespace so that libraries can extend... hmmm I am curious whether there are better alternative for "extending types" pattern

christian-bromann commented 7 months ago

Ah, thanks for recalling my memory on this. I am not aware of any way that allows to dynamically extend TypeScript interfaces. Currently we allow users to add custom commands to the Browser and Element and have this being reflected in the types which is pretty neat. Sure users could create their own Browser interface and extend from it but this approach feels easier to maintain.

Furthermore we want to provide type support for wdio.conf.ts files, especially the defined service and reporter options that people might install. Currently we use WebdriverIO.Config for that and allow services and reporters to extend service types so users only need to install the service or reporter and have type safe configurations. A way around this would be to have users define service like this:

import myService from 'wdio-my-service'

export const config = {
  // ...
  services: [myService({ ... })]
  // ...
}

Currently users only need to do this:

export const config = {
  // ...
  services: ['my']
  // ...
}

This has been historically been this way and something I am open to change since we migrated to TypeScript after we've defined these interfaces.

That said: if you know a way to allow us to easily extend the browser and element interface I am happy to explore ways to move to modules. Using namespaces has been challenging at times as you never know where the definitions come from.

xiaoyurenqa commented 7 months ago

Hi @christian-bromann @naruaway, thank you for your prompt responses!

Does this mean we should still be using WebdriverIO.Browser? I tried both methods today, it still couldn't find Browser when it's WebdriverIO.Browser, it has BrowserRunnerOptions, Config, Capabilities, etc., but no Browser.

On the other hand, import type { Browser } from "webdriverio"; works, which points to export type Browser<Mode extends 'sync' | 'async'> = Mode extends 'sync' ? BrowserSync : BrowserAsync;.

This may have something to do with the version of webdriverio and its types we are using (webdriverio: 8.1.3, @wdio/types: 8.1.2), at this point importing Browser is not deprecated yet.

christian-bromann commented 7 months ago

@xiaoyurenqa can you provide a minimal reproducible example where WebdriverIO.Browser doesn't work for you? As long as you have webdriverio installed as dependency it should have this registered to the namespace.

xiaoyurenqa commented 7 months ago

Hi @christian-bromann , I spun up a minimum TS project and tried it out, seems like WebdriverIO.Browser is being detected without a problem, it might have something to do with how our project was set up that for some reason Browser does not show up (but other types such as Config does)...

I'll bring this to our team to see if they have any idea on why this happens. Thank you!

xiaoyurenqa commented 7 months ago

At first glance, I think the reason it doesn't work for us is because WebdriverIO comes from @wdio/types/build, whereas when it works, the WebdriverIO comes from @wdio/globals.

christian-bromann commented 7 months ago

@xiaoyurenqa mhm 🤔 the WebdriverIO.Browser and WebdriverIO.Element types are defined as part of the webdriverio NPM package. Is it possible that your project has custom types defined in the tsconfig.json?

xiaoyurenqa commented 6 months ago

Hi @christian-bromann , yes seems like we have to specify @wdio/globals in order for WebdriverIO.Browser to work, thank you for your help! We can probably close this issue for now.

christian-bromann commented 6 months ago

Thanks for confirming!