Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Tracking doc: rewrite @wdio/sauce-service package into TypeScript #6001

Closed

Conversation

martinfrancois
Copy link
Contributor

Proposed changes

Rewrite @wdio/sauce-service package into TypeScript, see #5843

Types of changes

  • Bugfix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to not work as expected)
  • Documentation update

Checklist

  • I have read the CONTRIBUTING doc
  • I have added tests that prove my fix is effective or that my feature works
  • I have added necessary documentation (if appropriate)
  • I have added proper type definitions for new commands (if appropriate)

Further comments

Reviewers: @webdriverio/project-committers

packages/wdio-sauce-service/src/launcher.ts Outdated Show resolved Hide resolved
packages/wdio-sauce-service/tests/service.test.ts Outdated Show resolved Hide resolved
packages/wdio-utils/package.json Outdated Show resolved Hide resolved
packages/wdio-utils/utils.d.ts Outdated Show resolved Hide resolved
@martinfrancois martinfrancois force-pushed the 5843-ts-sauce-service branch 3 times, most recently from f8db4f8 to fd4258c Compare October 21, 2020 21:56
@martinfrancois
Copy link
Contributor Author

After rebasing I still get the error:

error TS2688: Cannot find type definition file for '@wdio/utils'.

(see builds here)

Any idea @christian-bromann ?

@martinfrancois
Copy link
Contributor Author

@christian-bromann I think it could be because of the order in which we build the typescript files here, as generated by ./scripts/build:

npx tsc -b packages/devtools/tsconfig.json packages/wdio-allure-reporter/tsconfig.json packages/wdio-appium-service/tsconfig.json packages/wdio-applitools-service/tsconfig.json packages/wdio-browserstack-service/tsconfig.json packages/wdio-cli/tsconfig.json packages/wdio-concise-reporter/tsconfig.json packages/wdio-config/tsconfig.json packages/wdio-crossbrowsertesting-service/tsconfig.json packages/wdio-cucumber-framework/tsconfig.json packages/wdio-devtools-service/tsconfig.json packages/wdio-dot-reporter/tsconfig.json packages/wdio-firefox-profile-service/tsconfig.json packages/wdio-jasmine-framework/tsconfig.json packages/wdio-junit-reporter/tsconfig.json packages/wdio-local-runner/tsconfig.json packages/wdio-logger/tsconfig.json packages/wdio-mocha-framework/tsconfig.json packages/wdio-repl/tsconfig.json packages/wdio-reporter/tsconfig.json packages/wdio-runner/tsconfig.json packages/wdio-sauce-service/tsconfig.json packages/wdio-selenium-standalone-service/tsconfig.json packages/wdio-shared-store-service/tsconfig.json packages/wdio-smoke-test-reporter/tsconfig.json packages/wdio-smoke-test-service/tsconfig.json packages/wdio-spec-reporter/tsconfig.json packages/wdio-static-server-service/tsconfig.json packages/wdio-sumologic-reporter/tsconfig.json packages/wdio-sync/tsconfig.json packages/wdio-testingbot-service/tsconfig.json packages/wdio-utils/tsconfig.json packages/wdio-webdriver-mock-service/tsconfig.json packages/webdriver/tsconfig.json packages/webdriverio/tsconfig.json

At least I noticed if I run the command npm run setup-full first, then when it fails I run node ./scripts/build I don't get the error anymore. So I suspect it could be that since wdio-utils is built AFTER wdio-sauce-service is built, but since wdio-sauce-service depends on wdio-utils, it throws an error since it doesn't have the types.

@martinfrancois martinfrancois force-pushed the 5843-ts-sauce-service branch 2 times, most recently from cd0aefb to 79201d3 Compare October 21, 2020 23:16
@martinfrancois
Copy link
Contributor Author

martinfrancois commented Oct 21, 2020

@christian-bromann sorry for the spam, but I now added a commit (79201d3) where I made it possible to define packages that should be compiled first, to avoid the issue I described in #6001 (comment)

Should I open up a separate PR for that or should we keep it in this one?

@christian-bromann
Copy link
Member

Good catch with the compile order. This could complain some other side issues I experienced.

@martinfrancois martinfrancois force-pushed the 5843-ts-sauce-service branch 2 times, most recently from 8304374 to 80e0845 Compare October 23, 2020 21:09
@martinfrancois
Copy link
Contributor Author

@christian-bromann just resolved your comments, thanks!

@martinfrancois martinfrancois force-pushed the 5843-ts-sauce-service branch 4 times, most recently from cff79b2 to 2431b46 Compare October 24, 2020 15:52
packages/wdio-sauce-service/src/service.ts Outdated Show resolved Hide resolved
packages/wdio-sauce-service/src/service.ts Outdated Show resolved Hide resolved
@@ -1,151 +0,0 @@
declare module WebdriverIO {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I didn't remove it though - only its contents, and I did so since they are outdated (and now included in node-saucelabs, do we need to do anything differently here?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

let's keep this file for now because it is still being referred to in the package.json ("types": "sauce-service.d.ts")

packages/wdio-sauce-service/src/service.ts Outdated Show resolved Hide resolved
packages/wdio-sauce-service/src/service.ts Outdated Show resolved Hide resolved
@martinfrancois martinfrancois force-pushed the 5843-ts-sauce-service branch 2 times, most recently from 10f6498 to 979d246 Compare October 27, 2020 00:49
@martinfrancois
Copy link
Contributor Author

@christian-bromann can you have a look again please? Seems like only one question from you that I answered is left.

Comment on lines 34 to 42
"devDependencies": {
"webdriverio": "6.7.0",
"webdriver": "6.7.0",
"@wdio/cucumber-framework": "6.7.0",
"@wdio/jasmine-framework": "6.7.0",
"@wdio/reporter": "6.7.0",
"@types/cucumber": "6.0.1",
"cucumber": "6.0.5"
},
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This will be problematic if we release. As we are in a production environment it won't install dev dependencies and therefor the compile step will fail. I would suggest:

  • remove cucumber
  • move @types/cucumber into the dependencies
  • reference all wdio types through tsconfig.json and use them through their namespace, e.g. WebDriver.DesiredCapabilities rather than importing them.

WDYT?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sounds good, I'll try it and see if it works and report back if I run into issues.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@christian-bromann by reference all wdio types through tsconfig.json you did mean to do it like that, right?:

"types": ["webdriverio", "webdriver", "@wdio/jasmine-framework" ,"@wdio/cucumber-framework", "@wdio/mocha-framework", "@wdio/utils"]

Or did you mean something else?

Because I'm getting an error, see launcher.ts:21:

TS2503: Cannot find namespace 'Webdriver'.
TS4063: Parameter 'capabilities' of constructor from exported class has or is using private name 'Webdriver'.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ok, I guess this solution doesn't work then and we have to use references like Jest does.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I googled a bit and came to the same conclusion, I also found this:
https://medium.com/@NiGhTTraX/how-to-set-up-a-typescript-monorepo-with-lerna-c6acda7d4559
https://github.com/NiGhTTraX/ts-monorepo

In this case, I guess we need to change the root tsconfig.json to the following, right?:

{
    "extends": "./tsconfig.prod.json",
    "compilerOptions": {
        "inlineSourceMap": true,
        "baseUrl": ".",
        "paths": {
            "@wdio/*": ["packages/wdio-*/src"],
            "devtools": ["packages/devtools/src"],
            "eslint-plugin-wdio": ["packages/eslint-plugin-wdio/src"],
            "webdriver": ["packages/webdriver/src"],
            "werbdriverio": ["packages/webdriverio/src"]
        }
    }
}

If the build command runs fine, does this indicate it will work in production as well?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Did you try specifying types in package.json? I hope this can solve typescript monorepo problem
https://www.typescriptlang.org/docs/handbook/declaration-files/publishing.html#including-declarations-in-your-npm-package

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@hieuxlu the source code is right there for you to check yourself, but yes I did so ;)

interface MultiRemoteBrowser extends WebdriverIO.BrowserObject {
instances: string[]
isMultiremote: boolean
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is not reusable for other services @christian-bromann @martinfrancois. We have browserstack service, testingbot services eagerly waiting to use multiremote browser types.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

do they have the same kind of type annotation? If so, we could think about extracting it somewhere more global, but anyways we could always move this in a later PR into a different package when there is the need for it.

Copy link
Member

@hieuxlu hieuxlu Oct 27, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes its common properties in global browser object, but not define yet. And yes, I agree it could be a separate issue. I just wonder if you guys could think of any better solution?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I personally like not to prematurely optimize things - my preferred approach is usually to adapt things to be as generic as they need to be, once it's needed. So I would rather start with having this interface here (as it's the only place it's used right now) and refactoring it to a different place as soon as the need for it arises. And where to put it then will be something to look at specifically in the context. Especially considering we are not exporting this type, so we don't run risk of any breaking changes when we move this to a different place.

const browserName = global.browser.instances.filter(
(browserName) => global.browser[browserName].sessionId === newSessionId)[0]
const browserName = this.multiRemoteBrowser.instances.filter(
(browserName: string) => (global.browser[browserName as keyof BrowserObject] as BrowserObject).sessionId === newSessionId)[0]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would say this as quite hacky @christian-bromann. browserName is definitely not in the list of keyof BrowserObject

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since the type of global.browser is BrowserObject, and you are trying to access a key from global.browser here, what else could it be other than a key of BrowserObject?

Copy link
Member

@hieuxlu hieuxlu Oct 27, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this is the case of accessing something like Record<string, BrowserObject>. If you use <keyOf>, the property will be casted to any, so the typecheck would provide no value.
So using global.browser[browserName as keyof BrowserObject] is equivalent to (global.browser as any)[browserName]

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you use <keyOf>, the property will be casted to any, so the typecheck would provide no value.

That's actually incorrect, have a look at the following example:

const myObj = {
  Hello: "world"
};
type ObjKey = keyof typeof myObj
const foo: ObjKey = "foo";

If what you are saying was correct, that keyof would cast it to any, then I would expect this example to compile without any errors (since ObjKey would be any). However, this results in the following error:

Initializer type "foo" is not assignable to variable type ObjKey

If I change the last line to be const foo: ObjKey = "Hello";, it compiles fine without any errors.
So clearly TypeScript preserves the types when using keyof.

But you are right in a way that by using browserName as keyof BrowserObject we are implicitly telling TypeScript to assume that browserName is a key of BrowserObject, even though it comes from this.multiRemoteBrowser.instances (which is a plain array of strings, with no guarantee of those strings being keyof BrowserObject). To be 100% correct we would need to either type instance as (keyof BrowserObject)[] and or run checks on runtime to ensure that instances are indeed keys of BrowserObject. But this would limit the instances that can be used, which could get annoying to maintain (as the code needs to be changed for each new type of instance) and it would most likely be overkill here and the risk of breaking things would be much higher than the benefit of the type safety we would gain in this situation, in my opinion.

Considering the code has been running fine previously, I'm quite hesitant with adding any more checks here, we could always consider improving type safety here in the future, but probably this would be a bigger undertaking. Of course I could be totally missing something, in which case I would be glad if you could point it out or make suggestions on how to improve the situation.

…ve `cucumber`

Signed-off-by: martinfrancois <f.martin@fastmail.com>
Signed-off-by: martinfrancois <f.martin@fastmail.com>
@christian-bromann
Copy link
Member

@martinfrancois there still seem to be some legit compiling errors:

Error: packages/wdio-sauce-service/src/launcher.ts(21,62): error TS2503: Cannot find namespace 'Webdriver'.
Error: packages/wdio-sauce-service/src/launcher.ts(21,100): error TS2503: Cannot find namespace 'Webdriver'.
Error: packages/wdio-sauce-service/src/launcher.ts(29,68): error TS2304: Cannot find name 'DesiredCapabilities'.

@martinfrancois
Copy link
Contributor Author

@christian-bromann yes, I mentioned the reason for them here: #6001 (comment)

@christian-bromann
Copy link
Member

@martinfrancois can we update the branch as have added a bunch of more rewrites to master.

I think the following way of handling external types works best: add webdriverio or any other sub package to the "types" properties in the subpackage tsconfig:

e.g. in packages/devtools/tsconfig.json:

{
    "extends": "../../tsconfig",
    "compilerOptions": {
        "baseUrl": ".",
        "outDir": "./build",
        "rootDir": "./src",
        "types": ["webdriverio", "@wdio/protocols"],
    },
    "include": [
        "src/**/*"
    ]
}

Then you can use the types in all files via, e.g. in packages/devtools/src/index.ts

static async newSession (options: WebDriver.Options = {}, modifier?: Function, userPrototype = {}, customCommandWrapper?: Function) {
        const params: WebDriver.Options = validateConfig(DEFAULTS, options)

Note: the difference between Webdriver (wrong namespace) and WebDriver (correct namespace).

Happy to help in any way to get this landed.

@christian-bromann
Copy link
Member

@martinfrancois do you plan to continue working on this?

@martinfrancois
Copy link
Contributor Author

@christian-bromann excuse me, didn't notice your update here: #6001 (comment)

Yes, I'll try your suggestion and will get back to you!

@christian-bromann
Copy link
Member

@martinfrancois we are aiming for a v7 release by end of January. Do you think you can push this over the line until then? If not, no problem. I am happy to pick this one up.

@christian-bromann
Copy link
Member

I will go ahead and take this over. I will use what you have done so far.

@christian-bromann
Copy link
Member

Closing in favor of #6274

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants