From 1a7d2626c5e876352dd83adb429d7a0e21c0e935 Mon Sep 17 00:00:00 2001 From: Christian Bromann Date: Wed, 20 Jan 2021 16:43:45 +0100 Subject: [PATCH] TypeScript set up overhaul (#6302) --- .gitignore | 3 +- .../css-shorthand-properties.d.ts | 0 .../src/types => @types}/css-value.d.ts | 0 .../src/types => @types}/custom.d.ts | 2 +- .../wdio/cucumber/features/my-feature.feature | 4 +- examples/wdio/cucumber/step-definitions.js | 2 +- jest.config.js | 2 +- package.json | 23 +- packages/devtools/package.json | 3 +- packages/devtools/src/commands.ts | 66 + packages/devtools/src/commands/acceptAlert.ts | 4 +- packages/devtools/src/commands/addCookie.ts | 8 +- packages/devtools/src/commands/back.ts | 4 +- packages/devtools/src/commands/closeWindow.ts | 7 +- .../devtools/src/commands/createWindow.ts | 17 +- .../devtools/src/commands/deleteAllCookies.ts | 4 +- .../devtools/src/commands/deleteCookie.ts | 4 +- .../devtools/src/commands/deleteSession.ts | 4 +- .../devtools/src/commands/dismissAlert.ts | 4 +- .../devtools/src/commands/elementClear.ts | 9 +- .../devtools/src/commands/elementClick.ts | 11 +- .../devtools/src/commands/elementSendKeys.ts | 11 +- .../src/commands/executeAsyncScript.ts | 11 +- .../devtools/src/commands/executeScript.ts | 11 +- packages/devtools/src/commands/findElement.ts | 9 +- .../src/commands/findElementFromElement.ts | 9 +- .../devtools/src/commands/findElements.ts | 9 +- .../src/commands/findElementsFromElement.ts | 9 +- packages/devtools/src/commands/forward.ts | 4 +- .../devtools/src/commands/getActiveElement.ts | 13 +- .../devtools/src/commands/getAlertText.ts | 4 +- .../devtools/src/commands/getAllCookies.ts | 4 +- .../src/commands/getElementAttribute.ts | 9 +- .../src/commands/getElementCSSValue.ts | 9 +- .../src/commands/getElementComputedLabel.ts | 5 +- .../src/commands/getElementComputedRole.ts | 5 +- .../src/commands/getElementProperty.ts | 7 +- .../devtools/src/commands/getElementRect.ts | 9 +- .../src/commands/getElementTagName.ts | 9 +- .../devtools/src/commands/getElementText.ts | 9 +- .../devtools/src/commands/getNamedCookie.ts | 4 +- .../devtools/src/commands/getPageSource.ts | 4 +- packages/devtools/src/commands/getTimeouts.ts | 4 +- packages/devtools/src/commands/getTitle.ts | 4 +- packages/devtools/src/commands/getUrl.ts | 7 +- .../devtools/src/commands/getWindowHandle.ts | 4 +- .../devtools/src/commands/getWindowHandles.ts | 7 +- .../devtools/src/commands/getWindowRect.ts | 7 +- .../devtools/src/commands/isElementEnabled.ts | 7 +- .../src/commands/isElementSelected.ts | 9 +- packages/devtools/src/commands/navigateTo.ts | 4 +- packages/devtools/src/commands/newSession.ts | 18 +- .../devtools/src/commands/performActions.ts | 17 +- packages/devtools/src/commands/refresh.ts | 4 +- .../devtools/src/commands/releaseActions.ts | 12 +- .../devtools/src/commands/sendAlertText.ts | 4 +- packages/devtools/src/commands/setTimeouts.ts | 4 +- .../devtools/src/commands/setWindowRect.ts | 4 +- packages/devtools/src/commands/status.ts | 11 +- .../devtools/src/commands/switchToFrame.ts | 17 +- .../src/commands/switchToParentFrame.ts | 8 +- .../devtools/src/commands/switchToWindow.ts | 4 +- .../src/commands/takeElementScreenshot.ts | 7 +- .../devtools/src/commands/takeScreenshot.ts | 4 +- packages/devtools/src/constants.ts | 4 +- packages/devtools/src/devtoolsdriver.ts | 6 +- packages/devtools/src/index.ts | 28 +- packages/devtools/src/launcher.ts | 17 +- packages/devtools/src/types.ts | 14 + packages/devtools/src/utils.ts | 10 +- .../tests/__snapshots__/devtools.test.ts.snap | 1 - packages/devtools/tsconfig.json | 5 +- packages/devtools/tsconfig.prod.json | 3 +- .../wdio-allure-reporter/allure-reporter.d.ts | 55 - packages/wdio-allure-reporter/package.json | 3 +- .../src/@types/allure-js-commons.ts | 100 - .../wdio-allure-reporter/src/constants.ts | 88 +- packages/wdio-allure-reporter/src/index.ts | 1320 ++++---- packages/wdio-allure-reporter/src/types.ts | 68 +- packages/wdio-allure-reporter/src/utils.ts | 222 +- packages/wdio-allure-reporter/tsconfig.json | 6 +- .../wdio-allure-reporter/tsconfig.prod.json | 6 +- packages/wdio-appium-service/package.json | 3 +- .../src/@types/appium-service.d.ts | 51 - packages/wdio-appium-service/src/index.ts | 9 + packages/wdio-appium-service/src/launcher.ts | 46 +- .../{appium-service.d.ts => src/types.ts} | 31 +- packages/wdio-appium-service/src/utils.ts | 4 +- .../tests/launcher.test.ts | 53 +- packages/wdio-appium-service/tsconfig.json | 3 +- .../wdio-appium-service/tsconfig.prod.json | 3 +- .../applitools-service.d.ts | 53 - packages/wdio-applitools-service/package.json | 6 +- .../src/@types/applitools-service.d.ts | 1 - packages/wdio-applitools-service/src/index.ts | 83 +- packages/wdio-applitools-service/src/types.ts | 37 + .../tests/service.test.ts | 11 +- .../wdio-applitools-service/tsconfig.json | 6 +- .../tsconfig.prod.json | 6 +- .../wdio-browserstack-service/package.json | 6 +- .../src/constants.ts | 4 +- .../wdio-browserstack-service/src/index.ts | 8 + .../wdio-browserstack-service/src/launcher.ts | 47 +- .../wdio-browserstack-service/src/service.ts | 97 +- .../wdio-browserstack-service/src/types.ts | 48 +- .../wdio-browserstack-service/src/util.ts | 21 +- .../tests/launcher.test.ts | 35 +- .../tests/service.test.ts | 525 ++- .../tests/util.test.ts | 29 +- .../wdio-browserstack-service/tsconfig.json | 6 +- .../tsconfig.prod.json | 3 +- packages/wdio-cli/package.json | 4 +- packages/wdio-cli/src/commands/config.ts | 2 +- packages/wdio-cli/src/commands/repl.ts | 12 +- packages/wdio-cli/src/index.ts | 1 + packages/wdio-cli/src/interface.ts | 8 +- packages/wdio-cli/src/launcher.ts | 37 +- packages/wdio-cli/src/types.ts | 4 +- packages/wdio-cli/src/utils.ts | 14 +- packages/wdio-cli/src/watcher.ts | 15 +- packages/wdio-cli/tests/commands/repl.test.ts | 7 +- packages/wdio-cli/tests/watcher.test.ts | 4 +- packages/wdio-cli/tsconfig.json | 3 +- packages/wdio-cli/tsconfig.prod.json | 3 +- packages/wdio-concise-reporter/package.json | 1 + packages/wdio-concise-reporter/src/index.ts | 17 +- packages/wdio-concise-reporter/tsconfig.json | 3 +- .../wdio-concise-reporter/tsconfig.prod.json | 3 +- packages/wdio-config/package.json | 1 + packages/wdio-config/src/constants.ts | 15 +- packages/wdio-config/src/index.ts | 3 - packages/wdio-config/src/lib/ConfigParser.ts | 32 +- packages/wdio-config/src/types.ts | 33 - packages/wdio-config/src/utils.ts | 12 +- packages/wdio-config/tests/utils.test.ts | 23 - packages/wdio-config/tsconfig.json | 20 +- packages/wdio-config/tsconfig.prod.json | 20 +- .../package.json | 6 +- .../src/index.ts | 8 + .../src/launcher.ts | 11 +- .../src/service.ts | 50 +- .../types.ts} | 6 +- .../tests/launcher.test.ts | 23 +- .../tests/service.test.ts | 144 +- .../tsconfig.json | 6 +- .../tsconfig.prod.json | 6 +- .../cucumber-framework.d.ts | 130 - packages/wdio-cucumber-framework/package.json | 5 +- .../wdio-cucumber-framework/src/constants.ts | 4 +- .../src/cucumberEventListener.ts | 3 +- packages/wdio-cucumber-framework/src/index.ts | 30 +- packages/wdio-cucumber-framework/src/types.ts | 56 +- packages/wdio-cucumber-framework/src/utils.ts | 7 +- .../wdio-cucumber-framework/tsconfig.json | 3 +- .../tsconfig.prod.json | 3 +- .../devtools-service.d.ts | 219 -- packages/wdio-devtools-service/package.json | 6 +- packages/wdio-devtools-service/src/auditor.ts | 3 +- .../wdio-devtools-service/src/commands.ts | 3 +- .../wdio-devtools-service/src/constants.ts | 2 +- packages/wdio-devtools-service/src/index.ts | 85 +- packages/wdio-devtools-service/src/types.ts | 114 + packages/wdio-devtools-service/src/utils.ts | 11 +- .../tests/service.test.ts | 2 +- .../wdio-devtools-service/tests/utils.test.ts | 10 +- packages/wdio-devtools-service/tsconfig.json | 6 +- .../wdio-devtools-service/tsconfig.prod.json | 6 +- packages/wdio-dot-reporter/package.json | 4 +- packages/wdio-dot-reporter/src/index.ts | 5 +- packages/wdio-dot-reporter/tsconfig.json | 3 +- packages/wdio-dot-reporter/tsconfig.prod.json | 3 +- .../firefox-profile-service.d.ts | 5 - .../wdio-firefox-profile-service/package.json | 3 +- .../wdio-firefox-profile-service/src/index.ts | 9 + .../src/launcher.ts | 15 +- .../wdio-firefox-profile-service/src/types.ts | 2 +- .../tsconfig.json | 6 +- .../tsconfig.prod.json | 6 +- .../jasmine-framework.d.ts | 90 - packages/wdio-jasmine-framework/package.json | 3 +- packages/wdio-jasmine-framework/src/index.ts | 20 +- .../tests/adapter.test.ts | 2 +- packages/wdio-jasmine-framework/tsconfig.json | 6 +- .../wdio-jasmine-framework/tsconfig.prod.json | 6 +- packages/wdio-junit-reporter/tsconfig.json | 3 +- .../wdio-junit-reporter/tsconfig.prod.json | 3 +- packages/wdio-local-runner/package.json | 4 +- packages/wdio-local-runner/src/index.ts | 11 +- packages/wdio-local-runner/src/types.ts | 4 +- packages/wdio-local-runner/src/worker.ts | 7 +- .../tests/localRunner.test.ts | 20 +- packages/wdio-local-runner/tests/repl.test.ts | 10 +- .../wdio-local-runner/tests/replQueue.test.ts | 14 +- packages/wdio-local-runner/tests/run.test.ts | 2 + .../wdio-local-runner/tests/stdStream.test.ts | 2 + .../tests/transformStream.test.ts | 2 + .../wdio-local-runner/tests/utils.test.ts | 2 + .../wdio-local-runner/tests/worker.test.ts | 20 +- packages/wdio-local-runner/tsconfig.json | 6 +- packages/wdio-local-runner/tsconfig.prod.json | 6 +- packages/wdio-logger/tsconfig.json | 3 +- packages/wdio-logger/tsconfig.prod.json | 3 +- .../wdio-mocha-framework/mocha-framework.d.ts | 104 - packages/wdio-mocha-framework/package.json | 6 +- packages/wdio-mocha-framework/src/index.ts | 19 +- packages/wdio-mocha-framework/src/types.ts | 79 +- .../wdio-mocha-framework/tests/utils.test.ts | 6 +- packages/wdio-mocha-framework/tsconfig.json | 3 +- .../wdio-mocha-framework/tsconfig.prod.json | 3 +- packages/wdio-protocols/index.d.ts | 92 - packages/wdio-protocols/index.js | 7 - packages/wdio-protocols/package.json | 4 +- packages/wdio-protocols/protocols/jsonwp.json | 8 +- .../wdio-protocols/protocols/saucelabs.json | 3 +- .../wdio-protocols/protocols/webdriver.json | 8 +- packages/wdio-protocols/src/index.ts | 58 + packages/wdio-protocols/src/types.ts | 178 + packages/wdio-protocols/tsconfig.json | 11 + packages/wdio-repl/tsconfig.json | 3 +- packages/wdio-repl/tsconfig.prod.json | 3 +- packages/wdio-reporter/package.json | 1 + packages/wdio-reporter/src/index.ts | 36 +- packages/wdio-reporter/src/stats/runner.ts | 11 +- packages/wdio-reporter/src/stats/test.ts | 1 - packages/wdio-reporter/src/utils.ts | 4 +- packages/wdio-reporter/tsconfig.json | 6 +- packages/wdio-reporter/tsconfig.prod.json | 6 +- packages/wdio-runner/package.json | 1 + packages/wdio-runner/src/index.ts | 96 +- packages/wdio-runner/src/reporter.ts | 62 +- packages/wdio-runner/src/utils.ts | 40 +- packages/wdio-runner/tsconfig.json | 3 +- packages/wdio-runner/tsconfig.prod.json | 3 +- packages/wdio-sauce-service/package.json | 6 +- packages/wdio-sauce-service/src/index.ts | 8 + packages/wdio-sauce-service/src/launcher.ts | 18 +- packages/wdio-sauce-service/src/service.ts | 67 +- packages/wdio-sauce-service/src/utils.ts | 7 +- .../wdio-sauce-service/tests/launcher.test.ts | 63 +- .../wdio-sauce-service/tests/service.test.ts | 197 +- packages/wdio-sauce-service/tsconfig.json | 6 +- .../wdio-sauce-service/tsconfig.prod.json | 6 +- .../launcher.ts | 2 - .../package.json | 3 +- .../src/index.ts | 12 + .../src/launcher.ts | 46 +- .../types.ts} | 16 +- .../tests/__mocks__/selenium-standalone.ts | 4 +- .../tests/launcher.test.ts | 42 +- .../tsconfig.json | 6 +- .../tsconfig.prod.json | 6 +- .../wdio-shared-store-service/package.json | 6 +- .../shared-store-service.d.ts | 8 - .../wdio-shared-store-service/src/client.ts | 8 +- .../wdio-shared-store-service/src/index.ts | 21 + .../wdio-shared-store-service/src/server.ts | 6 +- .../wdio-shared-store-service/src/service.ts | 21 +- .../wdio-shared-store-service/tsconfig.json | 6 +- .../tsconfig.prod.json | 6 +- .../wdio-smoke-test-reporter/package.json | 3 +- packages/wdio-smoke-test-service/package.json | 3 +- packages/wdio-spec-reporter/tsconfig.json | 3 +- .../wdio-spec-reporter/tsconfig.prod.json | 3 +- .../wdio-static-server-service/package.json | 3 +- .../wdio-static-server-service/src/index.ts | 8 + .../src/launcher.ts | 39 +- .../static-server-service.d.ts | 19 - .../tests/launcher.test.ts | 12 +- .../wdio-static-server-service/tsconfig.json | 3 +- .../tsconfig.prod.json | 3 +- packages/wdio-sumologic-reporter/package.json | 4 +- packages/wdio-sumologic-reporter/src/index.ts | 12 +- packages/wdio-sumologic-reporter/src/types.ts | 4 +- .../wdio-sumologic-reporter/tsconfig.json | 6 +- .../tsconfig.prod.json | 6 +- packages/wdio-sync/package.json | 8 +- packages/wdio-sync/src/index.ts | 8 +- packages/wdio-sync/src/runFnInFiberContext.ts | 4 +- packages/wdio-sync/src/wrapCommand.ts | 34 +- .../tests/executeHooksWithArgs.test.ts | 7 +- packages/wdio-sync/tests/fibers.test.ts | 2 +- packages/wdio-sync/tests/index.test.ts | 21 +- .../tests/runFnInFibersContext.test.ts | 16 +- packages/wdio-sync/tests/wrapCommand.test.ts | 27 +- packages/wdio-sync/tsconfig.json | 6 +- packages/wdio-sync/tsconfig.prod.json | 6 +- packages/wdio-sync/webdriverio-core.d.ts | 1436 --------- packages/wdio-sync/webdriverio.d.ts | 57 - packages/wdio-testingbot-service/package.json | 3 +- .../src/@types/testingbot-service.d.ts | 65 - .../@types/testingbot-tunnel-launcher.d.ts | 46 - packages/wdio-testingbot-service/src/index.ts | 10 +- .../wdio-testingbot-service/src/launcher.ts | 28 +- .../wdio-testingbot-service/src/service.ts | 60 +- .../{testingbot-service.d.ts => src/types.ts} | 33 +- .../tests/launcher.test.ts | 12 +- .../tests/service.test.ts | 267 +- .../wdio-testingbot-service/tsconfig.json | 6 +- .../tsconfig.prod.json | 6 +- packages/wdio-types/.npmignore | 2 + packages/wdio-types/README.md | 37 + packages/wdio-types/package.json | 34 + packages/wdio-types/src/Capabilities.ts | 678 ++++ packages/wdio-types/src/Clients.ts | 8 + packages/wdio-types/src/Frameworks.ts | 54 + packages/wdio-types/src/Options.ts | 420 +++ packages/wdio-types/src/Reporters.ts | 52 + packages/wdio-types/src/Services.ts | 251 ++ packages/wdio-types/src/index.ts | 34 + packages/wdio-types/tsconfig.json | 11 + packages/wdio-utils/package.json | 3 +- packages/wdio-utils/src/envDetector.ts | 30 +- packages/wdio-utils/src/index.ts | 8 +- packages/wdio-utils/src/initialisePlugin.ts | 4 +- packages/wdio-utils/src/initialiseServices.ts | 39 +- packages/wdio-utils/src/monad.ts | 6 +- packages/wdio-utils/src/shim.ts | 4 +- packages/wdio-utils/src/types.ts | 7 - packages/wdio-utils/src/utils.ts | 8 +- .../wdio-utils/tests/initialisePlugin.test.ts | 4 +- .../tests/initialiseServices.test.ts | 9 +- packages/wdio-utils/tests/shim-async.test.ts | 6 +- packages/wdio-utils/tsconfig.json | 8 +- packages/wdio-utils/tsconfig.prod.json | 4 +- .../wdio-webdriver-mock-service/package.json | 4 +- .../src/WebDriverMock.ts | 3 +- .../wdio-webdriver-mock-service/src/index.ts | 12 +- .../wdio-webdriver-mock-service/tsconfig.json | 6 +- .../tsconfig.prod.json | 6 +- packages/webdriver/package.json | 3 +- packages/webdriver/src/constants.ts | 5 +- packages/webdriver/src/index.ts | 24 +- packages/webdriver/src/request.ts | 8 +- packages/webdriver/src/types.ts | 878 +---- packages/webdriver/src/utils.ts | 19 +- packages/webdriver/tests/command.test.ts | 5 +- packages/webdriver/tests/constants.test.ts | 13 +- packages/webdriver/tests/index.test.ts | 14 +- packages/webdriver/tests/utils.test.ts | 12 +- packages/webdriver/tsconfig.json | 7 +- packages/webdriver/tsconfig.prod.json | 3 +- packages/webdriver/webdriver.d.ts | 2856 ----------------- packages/webdriverio/async.d.ts | 21 + packages/webdriverio/package.json | 5 +- packages/webdriverio/src/@types/async.d.ts | 27 + packages/webdriverio/src/commands/browser.ts | 69 +- .../webdriverio/src/commands/browser/$$.ts | 13 +- .../webdriverio/src/commands/browser/$.ts | 12 +- .../webdriverio/src/commands/browser/call.ts | 1 - .../src/commands/browser/custom$$.ts | 17 +- .../src/commands/browser/custom$.ts | 11 +- .../webdriverio/src/commands/browser/debug.ts | 9 +- .../src/commands/browser/deleteCookies.ts | 3 +- .../src/commands/browser/execute.ts | 4 +- .../src/commands/browser/executeAsync.ts | 4 +- .../src/commands/browser/getCookies.ts | 6 +- .../src/commands/browser/getPuppeteer.ts | 37 +- .../src/commands/browser/getWindowSize.ts | 17 +- .../webdriverio/src/commands/browser/keys.ts | 9 +- .../webdriverio/src/commands/browser/mock.ts | 28 +- .../src/commands/browser/newWindow.ts | 11 +- .../webdriverio/src/commands/browser/pause.ts | 3 +- .../src/commands/browser/react$$.ts | 23 +- .../src/commands/browser/react$.ts | 21 +- .../src/commands/browser/reloadSession.ts | 13 +- .../src/commands/browser/savePDF.ts | 25 +- .../commands/browser/saveRecordingScreen.ts | 9 +- .../src/commands/browser/saveScreenshot.ts | 9 +- .../src/commands/browser/setCookies.ts | 6 +- .../src/commands/browser/setTimeout.ts | 6 +- .../src/commands/browser/setWindowSize.ts | 23 +- .../src/commands/browser/switchWindow.ts | 4 +- .../src/commands/browser/throttle.ts | 84 +- .../src/commands/browser/touchAction.ts | 10 +- .../src/commands/browser/uploadFile.ts | 13 +- .../webdriverio/src/commands/browser/url.ts | 10 +- .../src/commands/browser/waitUntil.ts | 10 +- packages/webdriverio/src/commands/constant.ts | 16 +- packages/webdriverio/src/commands/element.ts | 107 +- .../webdriverio/src/commands/element/$.ts | 18 +- .../src/commands/element/addValue.ts | 16 +- .../webdriverio/src/commands/element/click.ts | 9 +- .../src/commands/element/custom$$.ts | 23 +- .../src/commands/element/custom$.ts | 12 +- .../src/commands/element/dragAndDrop.ts | 31 +- .../src/commands/element/getAttribute.ts | 1 - .../src/commands/element/getCSSProperty.ts | 7 +- .../src/commands/element/getComputedLabel.ts | 1 - .../src/commands/element/getComputedRole.ts | 1 - .../src/commands/element/getHTML.ts | 11 +- .../src/commands/element/getLocation.ts | 5 +- .../src/commands/element/getProperty.ts | 9 +- .../src/commands/element/getSize.ts | 13 +- .../src/commands/element/getTagName.ts | 1 - .../src/commands/element/getText.ts | 1 - .../src/commands/element/getValue.ts | 1 - .../src/commands/element/isClickable.ts | 10 +- .../src/commands/element/isDisplayed.ts | 19 +- .../commands/element/isDisplayedInViewport.ts | 10 +- .../src/commands/element/isEnabled.ts | 1 - .../src/commands/element/isEqual.ts | 21 +- .../src/commands/element/isExisting.ts | 7 +- .../src/commands/element/isFocused.ts | 11 +- .../src/commands/element/isSelected.ts | 1 - .../src/commands/element/moveTo.ts | 15 +- .../src/commands/element/nextElement.ts | 3 +- .../src/commands/element/parentElement.ts | 4 +- .../src/commands/element/previousElement.ts | 4 +- .../src/commands/element/react$$.ts | 21 +- .../src/commands/element/react$.ts | 19 +- .../src/commands/element/saveScreenshot.ts | 7 +- .../src/commands/element/scrollIntoView.ts | 7 +- .../src/commands/element/selectByAttribute.ts | 7 +- .../src/commands/element/selectByIndex.ts | 5 +- .../commands/element/selectByVisibleText.ts | 5 +- .../src/commands/element/setValue.ts | 5 +- .../src/commands/element/shadow$$.ts | 5 +- .../src/commands/element/shadow$.ts | 5 +- .../src/commands/element/touchAction.ts | 8 +- .../src/commands/element/waitForClickable.ts | 3 +- .../src/commands/element/waitForDisplayed.ts | 3 +- .../src/commands/element/waitForEnabled.ts | 4 +- .../src/commands/element/waitForExist.ts | 3 +- packages/webdriverio/src/constants.ts | 32 +- packages/webdriverio/src/index.ts | 61 +- packages/webdriverio/src/middlewares.ts | 5 +- packages/webdriverio/src/multiremote.ts | 48 +- packages/webdriverio/src/protocol-stub.ts | 12 +- packages/webdriverio/src/types.ts | 327 +- .../webdriverio/src/utils/getElementObject.ts | 21 +- .../webdriverio/src/utils/implicitWait.ts | 3 +- packages/webdriverio/src/utils/index.ts | 66 +- .../src/utils/interception/devtools.ts | 42 +- .../src/utils/interception/index.ts | 22 +- .../src/utils/interception/types.ts | 99 + .../src/utils/interception/webdriver.ts | 7 +- .../webdriverio/src/utils/refetchElement.ts | 8 +- packages/webdriverio/sync.d.ts | 21 + packages/webdriverio/tests/module.test.ts | 2 +- .../webdriverio/tests/multiremote.test.ts | 10 +- packages/webdriverio/tsconfig.json | 9 +- packages/webdriverio/tsconfig.prod.json | 6 +- packages/webdriverio/webdriverio-core.d.ts | 1436 --------- packages/webdriverio/webdriverio.d.ts | 83 - scripts/build.js | 4 +- scripts/templates/devtools.tpl.d.ts | 48 - scripts/templates/webdriver.tpl.d.ts | 54 - scripts/templates/webdriverio.tpl.d.ts | 760 ----- scripts/type-generation/constants.js | 13 - .../devtools-generate-typings.js | 45 - .../type-generation/generate-all-typings.js | 2 - scripts/type-generation/specific-types.json | 28 - .../webdriver-generate-typings.js | 82 +- .../webdriver-return-types.json | 9 +- .../webdriverio-generate-typings.js | 78 - tests/typings/cucumber/config.ts | 17 + .../tsconfig.json | 5 +- tests/typings/devtools/async.ts | 6 +- tests/typings/jasmine/config.ts | 9 + .../tsconfig.json | 4 +- tests/typings/mocha/config.ts | 8 + .../{sync-mocha => mocha}/tsconfig.json | 4 +- tests/typings/setup.js | 7 +- tests/typings/sync-applitools/applitools.ts | 28 - tests/typings/sync-applitools/tsconfig.json | 10 - .../typings/sync-browserstack/browserstack.ts | 15 - tests/typings/sync-browserstack/tsconfig.json | 10 - tests/typings/sync-cucumber/cucumber.ts | 28 - tests/typings/sync-cucumber/tsconfig.json | 10 - .../sync-devtools-service/devtools-service.ts | 44 - .../sync-devtools-service/tsconfig.json | 11 - tests/typings/sync-devtools/devtools.ts | 2 - tests/typings/sync-devtools/tsconfig.json | 12 - tests/typings/sync-jasmine/jasmine.ts | 25 - tests/typings/sync-jasmine/tsconfig.json | 10 - tests/typings/sync-mocha/mocha.ts | 28 - tests/typings/sync-saucelabs/saucelabs.ts | 15 - tests/typings/sync/applitools.ts | 7 + tests/typings/sync/config.ts | 54 +- tests/typings/sync/devtools.ts | 46 + tests/typings/sync/protocols.ts | 25 - tests/typings/sync/sync.ts | 78 +- tests/typings/sync/tsconfig.json | 22 +- tests/typings/sync/types/sync.d.ts | 10 - tests/typings/webdriver/async.ts | 37 + .../tsconfig.json | 4 +- .../webdriverio-applitools/applitools.ts | 24 - .../webdriverio-applitools/tsconfig.json | 10 - .../webdriverio-browserstack/browserstack.ts | 15 - .../webdriverio-browserstack/tsconfig.json | 10 - .../typings/webdriverio-cucumber/cucumber.ts | 12 - .../devtools-service.ts | 48 - .../tsconfig.json | 11 - tests/typings/webdriverio-jasmine/jasmine.ts | 16 - tests/typings/webdriverio-mocha/mocha.ts | 20 - tests/typings/webdriverio-mocha/tsconfig.json | 10 - .../webdriverio-saucelabs/saucelabs.ts | 15 - .../webdriverio-saucelabs/tsconfig.json | 10 - tests/typings/webdriverio/applitools.ts | 9 + tests/typings/webdriverio/async.ts | 142 +- tests/typings/webdriverio/config.ts | 94 +- tests/typings/webdriverio/devtools.ts | 46 + tests/typings/webdriverio/tsconfig.json | 14 +- tests/typings/webdriverio/types/async.d.ts | 10 - 504 files changed, 7083 insertions(+), 13174 deletions(-) rename {packages/webdriverio/src/types => @types}/css-shorthand-properties.d.ts (100%) rename {packages/webdriverio/src/types => @types}/css-value.d.ts (100%) rename {packages/webdriverio/src/types => @types}/custom.d.ts (81%) create mode 100644 packages/devtools/src/commands.ts create mode 100644 packages/devtools/src/types.ts delete mode 100644 packages/wdio-allure-reporter/allure-reporter.d.ts delete mode 100644 packages/wdio-allure-reporter/src/@types/allure-js-commons.ts delete mode 100644 packages/wdio-appium-service/src/@types/appium-service.d.ts rename packages/wdio-appium-service/{appium-service.d.ts => src/types.ts} (55%) delete mode 100644 packages/wdio-applitools-service/applitools-service.d.ts delete mode 120000 packages/wdio-applitools-service/src/@types/applitools-service.d.ts create mode 100644 packages/wdio-applitools-service/src/types.ts delete mode 100644 packages/wdio-config/src/types.ts rename packages/wdio-crossbrowsertesting-service/{crossbrowsertesting-service.d.ts => src/types.ts} (66%) delete mode 100644 packages/wdio-cucumber-framework/cucumber-framework.d.ts delete mode 100644 packages/wdio-devtools-service/devtools-service.d.ts delete mode 100644 packages/wdio-firefox-profile-service/firefox-profile-service.d.ts delete mode 100644 packages/wdio-jasmine-framework/jasmine-framework.d.ts delete mode 100644 packages/wdio-mocha-framework/mocha-framework.d.ts delete mode 100644 packages/wdio-protocols/index.d.ts delete mode 100644 packages/wdio-protocols/index.js create mode 100644 packages/wdio-protocols/src/index.ts create mode 100644 packages/wdio-protocols/src/types.ts create mode 100644 packages/wdio-protocols/tsconfig.json delete mode 100644 packages/wdio-selenium-standalone-service/launcher.ts rename packages/wdio-selenium-standalone-service/{selenium-standalone-service.d.ts => src/types.ts} (73%) delete mode 100644 packages/wdio-shared-store-service/shared-store-service.d.ts delete mode 100644 packages/wdio-static-server-service/static-server-service.d.ts delete mode 100644 packages/wdio-sync/webdriverio-core.d.ts delete mode 100644 packages/wdio-sync/webdriverio.d.ts delete mode 100644 packages/wdio-testingbot-service/src/@types/testingbot-service.d.ts rename packages/wdio-testingbot-service/{testingbot-service.d.ts => src/types.ts} (85%) create mode 100644 packages/wdio-types/.npmignore create mode 100644 packages/wdio-types/README.md create mode 100644 packages/wdio-types/package.json create mode 100644 packages/wdio-types/src/Capabilities.ts create mode 100644 packages/wdio-types/src/Clients.ts create mode 100644 packages/wdio-types/src/Frameworks.ts create mode 100644 packages/wdio-types/src/Options.ts create mode 100644 packages/wdio-types/src/Reporters.ts create mode 100644 packages/wdio-types/src/Services.ts create mode 100644 packages/wdio-types/src/index.ts create mode 100644 packages/wdio-types/tsconfig.json delete mode 100644 packages/wdio-utils/src/types.ts delete mode 100644 packages/webdriver/webdriver.d.ts create mode 100644 packages/webdriverio/async.d.ts create mode 100644 packages/webdriverio/src/@types/async.d.ts create mode 100644 packages/webdriverio/src/utils/interception/types.ts create mode 100644 packages/webdriverio/sync.d.ts delete mode 100644 packages/webdriverio/webdriverio-core.d.ts delete mode 100644 packages/webdriverio/webdriverio.d.ts delete mode 100644 scripts/templates/devtools.tpl.d.ts delete mode 100644 scripts/templates/webdriver.tpl.d.ts delete mode 100644 scripts/templates/webdriverio.tpl.d.ts delete mode 100644 scripts/type-generation/constants.js delete mode 100644 scripts/type-generation/devtools-generate-typings.js delete mode 100644 scripts/type-generation/specific-types.json delete mode 100644 scripts/type-generation/webdriverio-generate-typings.js create mode 100644 tests/typings/cucumber/config.ts rename tests/typings/{webdriverio-cucumber => cucumber}/tsconfig.json (53%) create mode 100644 tests/typings/jasmine/config.ts rename tests/typings/{webdriverio-jasmine => jasmine}/tsconfig.json (60%) create mode 100644 tests/typings/mocha/config.ts rename tests/typings/{sync-mocha => mocha}/tsconfig.json (60%) delete mode 100644 tests/typings/sync-applitools/applitools.ts delete mode 100644 tests/typings/sync-applitools/tsconfig.json delete mode 100644 tests/typings/sync-browserstack/browserstack.ts delete mode 100644 tests/typings/sync-browserstack/tsconfig.json delete mode 100644 tests/typings/sync-cucumber/cucumber.ts delete mode 100644 tests/typings/sync-cucumber/tsconfig.json delete mode 100644 tests/typings/sync-devtools-service/devtools-service.ts delete mode 100644 tests/typings/sync-devtools-service/tsconfig.json delete mode 100644 tests/typings/sync-devtools/devtools.ts delete mode 100644 tests/typings/sync-devtools/tsconfig.json delete mode 100644 tests/typings/sync-jasmine/jasmine.ts delete mode 100644 tests/typings/sync-jasmine/tsconfig.json delete mode 100644 tests/typings/sync-mocha/mocha.ts delete mode 100644 tests/typings/sync-saucelabs/saucelabs.ts create mode 100644 tests/typings/sync/applitools.ts create mode 100644 tests/typings/sync/devtools.ts delete mode 100644 tests/typings/sync/protocols.ts delete mode 100644 tests/typings/sync/types/sync.d.ts create mode 100644 tests/typings/webdriver/async.ts rename tests/typings/{sync-saucelabs => webdriver}/tsconfig.json (67%) delete mode 100644 tests/typings/webdriverio-applitools/applitools.ts delete mode 100644 tests/typings/webdriverio-applitools/tsconfig.json delete mode 100644 tests/typings/webdriverio-browserstack/browserstack.ts delete mode 100644 tests/typings/webdriverio-browserstack/tsconfig.json delete mode 100644 tests/typings/webdriverio-cucumber/cucumber.ts delete mode 100644 tests/typings/webdriverio-devtools-service/devtools-service.ts delete mode 100644 tests/typings/webdriverio-devtools-service/tsconfig.json delete mode 100644 tests/typings/webdriverio-jasmine/jasmine.ts delete mode 100644 tests/typings/webdriverio-mocha/mocha.ts delete mode 100644 tests/typings/webdriverio-mocha/tsconfig.json delete mode 100644 tests/typings/webdriverio-saucelabs/saucelabs.ts delete mode 100644 tests/typings/webdriverio-saucelabs/tsconfig.json create mode 100644 tests/typings/webdriverio/applitools.ts create mode 100644 tests/typings/webdriverio/devtools.ts delete mode 100644 tests/typings/webdriverio/types/async.d.ts diff --git a/.gitignore b/.gitignore index 340a11534c9..f3aa74a6656 100644 --- a/.gitignore +++ b/.gitignore @@ -37,8 +37,9 @@ node_modules/ jspm_packages/ # Typescript v1 declaration files -typings/ +/packages/wdio-protocols/src/commands !tests/typings +!typings/types.ts tests/typings/**/dist # Optional npm cache directory diff --git a/packages/webdriverio/src/types/css-shorthand-properties.d.ts b/@types/css-shorthand-properties.d.ts similarity index 100% rename from packages/webdriverio/src/types/css-shorthand-properties.d.ts rename to @types/css-shorthand-properties.d.ts diff --git a/packages/webdriverio/src/types/css-value.d.ts b/@types/css-value.d.ts similarity index 100% rename from packages/webdriverio/src/types/css-value.d.ts rename to @types/css-value.d.ts diff --git a/packages/webdriverio/src/types/custom.d.ts b/@types/custom.d.ts similarity index 81% rename from packages/webdriverio/src/types/custom.d.ts rename to @types/custom.d.ts index 4c089ffaf75..e31dfb5bc5e 100644 --- a/packages/webdriverio/src/types/custom.d.ts +++ b/@types/custom.d.ts @@ -1,4 +1,4 @@ -namespace NodeJS { +declare namespace NodeJS { interface Process { _debugProcess: (pid: number) => void _debugEnd: (pid: number) => void diff --git a/examples/wdio/cucumber/features/my-feature.feature b/examples/wdio/cucumber/features/my-feature.feature index 9fc80fbe558..dba2b728eac 100644 --- a/examples/wdio/cucumber/features/my-feature.feature +++ b/examples/wdio/cucumber/features/my-feature.feature @@ -5,11 +5,11 @@ Feature: Example feature Scenario: Get size of an element Given I go on the website "https://github.com/" - Then should the element ".header-logged-out a" be 32px wide and 35px high + Then should the element ".header-logged-out a" be 32px wide and 34px high Scenario: Get title of website Given I go on the website "https://github.com/" - Then should the title of the page be "The world’s leading software development platform · GitHub" + Then should the title of the page be "GitHub: Where the world builds software · GitHub" Scenario: Data Tables Given I go on the website "http://todomvc.com/examples/react/#/" diff --git a/examples/wdio/cucumber/step-definitions.js b/examples/wdio/cucumber/step-definitions.js index 77e971d0e4c..fe9f5155e4d 100644 --- a/examples/wdio/cucumber/step-definitions.js +++ b/examples/wdio/cucumber/step-definitions.js @@ -9,7 +9,7 @@ * $ cucumber.js */ -const { Given, When, Then } = require('cucumber') +const { Given, When, Then } = require('@cucumber/cucumber') Given(/^I go on the website "([^"]*)"$/, (url) => { browser.url(url) diff --git a/jest.config.js b/jest.config.js index 389f5b9953c..6e0ba62629f 100644 --- a/jest.config.js +++ b/jest.config.js @@ -25,7 +25,7 @@ module.exports = { collectCoverage: true, coverageThreshold: { global: { - branches: 93, + branches: 92, functions: 97, lines: 98, statements: 98 diff --git a/package.json b/package.json index c3a0f5dc19e..2a309b2e852 100644 --- a/package.json +++ b/package.json @@ -33,30 +33,20 @@ "test": "run-s test:depcheck test:eslint test:typings test:coverage test:smoke", "test:depcheck": "node ./scripts/depcheck.js", "test:eslint": "eslint --cache packages examples scripts tests", - "test:typings": "npm run generate:typings && node tests/typings/setup && npm run ts && npm run clean:tests && node tests/typings/setup --ts=3.8 && npm run ts && npm run clean:tests", + "test:typings": "node tests/typings/setup && npm run ts && npm run clean:tests && node tests/typings/setup --ts=3.9 && npm run ts && npm run clean:tests", "test:coverage": "node --max-old-space-size=8192 ./node_modules/jest/bin/jest.js --coverage --logHeapUsage", "test:smoke": "ts-node ./tests/smoke.runner.js", "test:e2e": "jest --config e2e/jest.config.js", "test:e2e:edge": "npm run test:e2e -- --testMatch '**/edge.e2e.js'", "test:e2e:firefox": "npm run test:e2e -- --testMatch '/standalone/firefox.e2e.js'", "ts": "run-p ts:*", - "ts:sync": "cd tests/typings/sync && npx tsc", + "ts:webdriver": "cd tests/typings/webdriver && npx tsc", "ts:devtools": "cd tests/typings/devtools && npx tsc", - "ts:sync-applitools": "cd tests/typings/sync-applitools && npx tsc", - "ts:sync-browserstack": "cd tests/typings/sync-browserstack && npx tsc", - "ts:sync-devtools-service": "cd tests/typings/sync-devtools-service && npx tsc", - "ts:sync-saucelabs": "cd tests/typings/sync-saucelabs && npx tsc", - "ts:sync-mocha": "cd tests/typings/sync-mocha && npx tsc", - "ts:sync-jasmine": "cd tests/typings/sync-jasmine && npx tsc", - "ts:sync-cucumber": "cd tests/typings/sync-cucumber && npx tsc", "ts:webdriverio": "cd tests/typings/webdriverio && npx tsc", - "ts:webdriverio-applitools": "cd tests/typings/webdriverio-applitools && npx tsc", - "ts:webdriverio-browserstack": "cd tests/typings/webdriverio-browserstack && npx tsc", - "ts:webdriverio-saucelabs": "cd tests/typings/webdriverio-saucelabs && npx tsc", - "ts:webdriverio-devtools": "cd tests/typings/sync-devtools && npx tsc", - "ts:webdriverio-devtools-service": "cd tests/typings/webdriverio-devtools-service && npx tsc", - "ts:webdriverio-mocha": "cd tests/typings/webdriverio-mocha && npx tsc", - "ts:webdriverio-jasmine": "cd tests/typings/webdriverio-jasmine && npx tsc", + "ts:mocha": "cd tests/typings/mocha && npx tsc", + "ts:jasmine": "cd tests/typings/jasmine && npx tsc", + "ts:cucumber": "cd tests/typings/cucumber && npx tsc", + "ts:sync": "cd tests/typings/sync && npx tsc", "watch": "node ./scripts/build --watch", "version": "npm run changelog && git add CHANGELOG.md" }, @@ -82,6 +72,7 @@ "@typescript-eslint/eslint-plugin": "^4.4.0", "@typescript-eslint/parser": "^4.4.0", "aws-sdk": "^2.539.0", + "camelcase": "^6.2.0", "cheerio": "^1.0.0-rc.3", "codecov": "^3.6.1", "copyfiles": "^2.1.1", diff --git a/packages/devtools/package.json b/packages/devtools/package.json index 5d6cc1c7364..49c40f7c327 100644 --- a/packages/devtools/package.json +++ b/packages/devtools/package.json @@ -9,7 +9,7 @@ "engines": { "node": ">=12.0.0" }, - "types": "./devtools.d.ts", + "types": "./build/index.d.ts", "typeScriptVersion": "3.8.3", "repository": { "type": "git", @@ -25,6 +25,7 @@ "@wdio/config": "6.12.1", "@wdio/logger": "6.10.10", "@wdio/protocols": "6.12.0", + "@wdio/types": "^6.10.6", "@wdio/utils": "6.11.0", "chrome-launcher": "^0.13.1", "edge-paths": "^2.1.0", diff --git a/packages/devtools/src/commands.ts b/packages/devtools/src/commands.ts new file mode 100644 index 00000000000..79349744a97 --- /dev/null +++ b/packages/devtools/src/commands.ts @@ -0,0 +1,66 @@ +import acceptAlert from './commands/acceptAlert' +import addCookie from './commands/addCookie' +import back from './commands/back' +import closeWindow from './commands/closeWindow' +import createWindow from './commands/createWindow' +import deleteAllCookies from './commands/deleteAllCookies' +import deleteCookie from './commands/deleteCookie' +import deleteSession from './commands/deleteSession' +import dismissAlert from './commands/dismissAlert' +import elementClear from './commands/elementClear' +import elementClick from './commands/elementClick' +import elementSendKeys from './commands/elementSendKeys' +import executeAsyncScript from './commands/executeAsyncScript' +import executeScript from './commands/executeScript' +import findElement from './commands/findElement' +import findElementFromElement from './commands/findElementFromElement' +import findElements from './commands/findElements' +import findElementsFromElement from './commands/findElementsFromElement' +import forward from './commands/forward' +import getActiveElement from './commands/getActiveElement' +import getAlertText from './commands/getAlertText' +import getAllCookies from './commands/getAllCookies' +import getElementAttribute from './commands/getElementAttribute' +import getElementCSSValue from './commands/getElementCSSValue' +import getElementComputedLabel from './commands/getElementComputedLabel' +import getElementComputedRole from './commands/getElementComputedRole' +import getElementProperty from './commands/getElementProperty' +import getElementRect from './commands/getElementRect' +import getElementTagName from './commands/getElementTagName' +import getElementText from './commands/getElementText' +import getNamedCookie from './commands/getNamedCookie' +import getPageSource from './commands/getPageSource' +import getTimeouts from './commands/getTimeouts' +import getTitle from './commands/getTitle' +import getUrl from './commands/getUrl' +import getWindowHandle from './commands/getWindowHandle' +import getWindowHandles from './commands/getWindowHandles' +import getWindowRect from './commands/getWindowRect' +import isElementEnabled from './commands/isElementEnabled' +import isElementSelected from './commands/isElementSelected' +import navigateTo from './commands/navigateTo' +import newSession from './commands/newSession' +import performActions from './commands/performActions' +import refresh from './commands/refresh' +import releaseActions from './commands/releaseActions' +import sendAlertText from './commands/sendAlertText' +import setTimeouts from './commands/setTimeouts' +import setWindowRect from './commands/setWindowRect' +import status from './commands/status' +import switchToFrame from './commands/switchToFrame' +import switchToParentFrame from './commands/switchToParentFrame' +import switchToWindow from './commands/switchToWindow' +import takeElementScreenshot from './commands/takeElementScreenshot' +import takeScreenshot from './commands/takeScreenshot' + +export { + acceptAlert, addCookie, back, closeWindow, createWindow, deleteAllCookies, deleteCookie, deleteSession, + dismissAlert, elementClear, elementClick, elementSendKeys, executeAsyncScript, executeScript, findElement, + findElementFromElement, findElements, findElementsFromElement, forward, getActiveElement, getAlertText, + getAllCookies, getElementAttribute, getElementCSSValue, getElementComputedLabel, getElementComputedRole, + getElementProperty, getElementRect, getElementTagName, getElementText, getNamedCookie, getPageSource, + getTimeouts, getTitle, getUrl, getWindowHandle, getWindowHandles, getWindowRect, isElementEnabled, + isElementSelected, navigateTo, newSession, performActions, refresh, releaseActions, sendAlertText, + setTimeouts, setWindowRect, status, switchToFrame, switchToParentFrame, switchToWindow, takeElementScreenshot, + takeScreenshot +} diff --git a/packages/devtools/src/commands/acceptAlert.ts b/packages/devtools/src/commands/acceptAlert.ts index 7aaaa45dcc5..ab36a661018 100644 --- a/packages/devtools/src/commands/acceptAlert.ts +++ b/packages/devtools/src/commands/acceptAlert.ts @@ -1,11 +1,11 @@ +import type DevToolsDriver from '../devtoolsdriver' + /** * The Accept Alert command accepts a simple dialog if present, otherwise error. * * @alias browser.acceptAlert * @see https://w3c.github.io/webdriver/#dfn-accept-alert */ -import type DevToolsDriver from '../devtoolsdriver' - export default async function acceptAlert(this: DevToolsDriver) { if (!this.activeDialog) { throw new Error('no such alert') diff --git a/packages/devtools/src/commands/addCookie.ts b/packages/devtools/src/commands/addCookie.ts index 947c78e4bcb..55d0186083c 100644 --- a/packages/devtools/src/commands/addCookie.ts +++ b/packages/devtools/src/commands/addCookie.ts @@ -1,3 +1,7 @@ +import { Cookie } from '@wdio/protocols' + +import type DevToolsDriver from '../devtoolsdriver' + /** * The Add Cookie command adds a single cookie to the cookie store * associated with the active document's address. @@ -6,11 +10,9 @@ * @see https://w3c.github.io/webdriver/#dfn-adding-a-cookie * @param {object} cookie A JSON object representing a cookie. It must have at least the name and value fields and could have more, including expiry-time and so on */ -import type DevToolsDriver from '../devtoolsdriver' - export default async function addCookie( this: DevToolsDriver, - { cookie }: { cookie: WebDriver.Cookie } + { cookie }: { cookie: Cookie } ) { const page = this.getPageHandle() diff --git a/packages/devtools/src/commands/back.ts b/packages/devtools/src/commands/back.ts index 1fd51b724d7..815e514d6ad 100644 --- a/packages/devtools/src/commands/back.ts +++ b/packages/devtools/src/commands/back.ts @@ -1,3 +1,5 @@ +import type DevToolsDriver from '../devtoolsdriver' + /** * The Back command causes the browser to traverse one step backward * in the joint session history of the current top-level browsing context. @@ -6,8 +8,6 @@ * @alias browser.back * @see https://w3c.github.io/webdriver/#dfn-back */ -import type DevToolsDriver from '../devtoolsdriver' - export default async function back (this: DevToolsDriver) { delete this.currentFrame const page = this.getPageHandle() diff --git a/packages/devtools/src/commands/closeWindow.ts b/packages/devtools/src/commands/closeWindow.ts index 189ae87f943..14a234f2093 100644 --- a/packages/devtools/src/commands/closeWindow.ts +++ b/packages/devtools/src/commands/closeWindow.ts @@ -1,3 +1,6 @@ +import { v4 as uuidv4 } from 'uuid' +import type DevToolsDriver from '../devtoolsdriver' + /** * The Close Window command closes the current top-level browsing context. * Once done, if there are no more top-level browsing contexts open, @@ -6,10 +9,6 @@ * @alias browser.closeWindow * @see https://w3c.github.io/webdriver/#dfn-close-window */ - -import { v4 as uuidv4 } from 'uuid' -import type DevToolsDriver from '../devtoolsdriver' - export default async function closeWindow (this: DevToolsDriver) { delete this.currentFrame diff --git a/packages/devtools/src/commands/createWindow.ts b/packages/devtools/src/commands/createWindow.ts index d105f6c516e..76cf878e36a 100644 --- a/packages/devtools/src/commands/createWindow.ts +++ b/packages/devtools/src/commands/createWindow.ts @@ -1,12 +1,3 @@ -/** - * Create a new top-level browsing context. - * - * @alias browser.createWindow - * @see https://w3c.github.io/webdriver/#new-window - * @param {string} type Set to 'tab' if the newly created window shares an OS-level window with the current browsing context, or 'window' otherwise. - * @return {object} New window object containing 'handle' with the value of the handle and 'type' with the value of the created window type - */ - import { v4 as uuidv4 } from 'uuid' import type DevToolsDriver from '../devtoolsdriver' @@ -16,6 +7,14 @@ const WINDOW_FEATURES = 'menubar=1,toolbar=1,location=1,resizable=1,scrollbars=1 const NEW_PAGE_URL = 'about:blank' const DEFAULT_WINDOW_TYPE = 'tab' +/** + * Create a new top-level browsing context. + * + * @alias browser.createWindow + * @see https://w3c.github.io/webdriver/#new-window + * @param {string} type Set to 'tab' if the newly created window shares an OS-level window with the current browsing context, or 'window' otherwise. + * @return {object} New window object containing 'handle' with the value of the handle and 'type' with the value of the created window type + */ export default async function createWindow ( this: DevToolsDriver, { type }: { type: 'window' | 'tab' } diff --git a/packages/devtools/src/commands/deleteAllCookies.ts b/packages/devtools/src/commands/deleteAllCookies.ts index f96e597ad73..fd8ffef87b2 100644 --- a/packages/devtools/src/commands/deleteAllCookies.ts +++ b/packages/devtools/src/commands/deleteAllCookies.ts @@ -1,3 +1,5 @@ +import type DevToolsDriver from '../devtoolsdriver' + /** * The Delete All Cookies command allows deletion of all cookies * associated with the active document's address. @@ -5,8 +7,6 @@ * @alias browser.deleteAllCookies * @see https://w3c.github.io/webdriver/#dfn-delete-all-cookies */ -import type DevToolsDriver from '../devtoolsdriver' - export default async function deleteAllCookies (this: DevToolsDriver) { const page = this.getPageHandle() const cookies = await page.cookies() diff --git a/packages/devtools/src/commands/deleteCookie.ts b/packages/devtools/src/commands/deleteCookie.ts index 6e1f6353dff..8d8867cb656 100644 --- a/packages/devtools/src/commands/deleteCookie.ts +++ b/packages/devtools/src/commands/deleteCookie.ts @@ -1,3 +1,5 @@ +import type DevToolsDriver from '../devtoolsdriver' + /** * The Delete Cookie command allows you to delete either a single cookie by parameter name, * or all the cookies associated with the active document's address if name is undefined. @@ -6,8 +8,6 @@ * @see https://w3c.github.io/webdriver/#dfn-delete-cookie * @param {string} name name of the cookie to delete */ -import type DevToolsDriver from '../devtoolsdriver' - export default async function deleteCookie ( this: DevToolsDriver, { name }: { name: string } diff --git a/packages/devtools/src/commands/deleteSession.ts b/packages/devtools/src/commands/deleteSession.ts index 8b298eae0e1..6d357bedfd4 100644 --- a/packages/devtools/src/commands/deleteSession.ts +++ b/packages/devtools/src/commands/deleteSession.ts @@ -1,3 +1,5 @@ +import type DevToolsDriver from '../devtoolsdriver' + /** * The Delete Session command closes any top-level browsing contexts associated * with the current session, terminates the connection, and finally closes the current session. @@ -5,8 +7,6 @@ * @alias browser.deleteSession * @see https://w3c.github.io/webdriver/#dfn-delete-session */ -import type DevToolsDriver from '../devtoolsdriver' - export default async function deleteSession (this: DevToolsDriver) { await this.browser.close() this.windows.clear() diff --git a/packages/devtools/src/commands/dismissAlert.ts b/packages/devtools/src/commands/dismissAlert.ts index a5b1a363ad9..2b75e982044 100644 --- a/packages/devtools/src/commands/dismissAlert.ts +++ b/packages/devtools/src/commands/dismissAlert.ts @@ -1,3 +1,5 @@ +import type DevToolsDriver from '../devtoolsdriver' + /** * The Dismiss Alert command dismisses a simple dialog if present, otherwise error. * A request to dismiss an alert user prompt, which may not necessarily have a dismiss button, @@ -6,8 +8,6 @@ * @alias browser.dismissAlert * @see https://w3c.github.io/webdriver/#dfn-dismiss-alert */ -import type DevToolsDriver from '../devtoolsdriver' - export default async function dismissAlert (this: DevToolsDriver) { if (!this.activeDialog) { throw new Error('no such alert') diff --git a/packages/devtools/src/commands/elementClear.ts b/packages/devtools/src/commands/elementClear.ts index fa1f8f3ef51..83f647a0f6c 100644 --- a/packages/devtools/src/commands/elementClear.ts +++ b/packages/devtools/src/commands/elementClear.ts @@ -1,3 +1,7 @@ +import command from '../scripts/elementClear' +import { getStaleElementError } from '../utils' +import type DevToolsDriver from '../devtoolsdriver' + /** * The Element Clear command scrolls into view an editable or resettable element and then attempts * to clear its selected files or text content. @@ -6,11 +10,6 @@ * @see https://w3c.github.io/webdriver/#dfn-element-clear * @param {string} elementId the id of an element returned in a previous call to Find Element(s) */ - -import command from '../scripts/elementClear' -import { getStaleElementError } from '../utils' -import type DevToolsDriver from '../devtoolsdriver' - export default async function elementClear ( this: DevToolsDriver, { elementId }: { elementId: string } diff --git a/packages/devtools/src/commands/elementClick.ts b/packages/devtools/src/commands/elementClick.ts index f312623536a..da93b0fb0f2 100644 --- a/packages/devtools/src/commands/elementClick.ts +++ b/packages/devtools/src/commands/elementClick.ts @@ -1,3 +1,8 @@ +import getElementTagName from './getElementTagName' +import selectOptionScript from '../scripts/selectOption' +import { getStaleElementError } from '../utils' +import type DevToolsDriver from '../devtoolsdriver' + /** * The Element Click command scrolls into view the element if it is not already pointer-interactable, * and clicks its in-view center point. If the element's center point is obscured by another element, @@ -9,12 +14,6 @@ * @see https://w3c.github.io/webdriver/#dfn-element-click * @param {string} elementId the id of an element returned in a previous call to Find Element(s) */ - -import getElementTagName from './getElementTagName' -import selectOptionScript from '../scripts/selectOption' -import { getStaleElementError } from '../utils' -import type DevToolsDriver from '../devtoolsdriver' - export default async function elementClick ( this: DevToolsDriver, { elementId }: { elementId: string } diff --git a/packages/devtools/src/commands/elementSendKeys.ts b/packages/devtools/src/commands/elementSendKeys.ts index a1e79767a9d..b4bc1aca2b0 100644 --- a/packages/devtools/src/commands/elementSendKeys.ts +++ b/packages/devtools/src/commands/elementSendKeys.ts @@ -1,3 +1,8 @@ +import path from 'path' + +import { getStaleElementError } from '../utils' +import type DevToolsDriver from '../devtoolsdriver' + /** * The Element Send Keys command scrolls into view the form control element and then sends * the provided keys to the element. In case the element is not keyboard-interactable, @@ -9,12 +14,6 @@ * @param {string} elementId the id of an element returned in a previous call to Find Element(s) * @param {string} text string to send as keystrokes to the element */ - -import path from 'path' - -import { getStaleElementError } from '../utils' -import type DevToolsDriver from '../devtoolsdriver' - export default async function elementSendKeys ( this: DevToolsDriver, { elementId, text }: { elementId: string, text: string } diff --git a/packages/devtools/src/commands/executeAsyncScript.ts b/packages/devtools/src/commands/executeAsyncScript.ts index 131e7ed4638..2543dac99ed 100644 --- a/packages/devtools/src/commands/executeAsyncScript.ts +++ b/packages/devtools/src/commands/executeAsyncScript.ts @@ -1,3 +1,8 @@ +import command from '../scripts/executeAsyncScript' +import { transformExecuteArgs, transformExecuteResult } from '../utils' +import { SERIALIZE_PROPERTY, SERIALIZE_FLAG } from '../constants' +import type DevToolsDriver from '../devtoolsdriver' + /** * The Execute Async Script command causes JavaScript to execute as an anonymous function. * Unlike the Execute Script command, the result of the function is ignored. @@ -10,12 +15,6 @@ * @param {*[]} args an array of JSON values which will be deserialized and passed as arguments to your function * @return * Either the return value of your script, the fulfillment of the Promise returned by your script, or the error which was the reason for your script's returned Promise's rejection. */ - -import command from '../scripts/executeAsyncScript' -import { transformExecuteArgs, transformExecuteResult } from '../utils' -import { SERIALIZE_PROPERTY, SERIALIZE_FLAG } from '../constants' -import type DevToolsDriver from '../devtoolsdriver' - export default async function executeAsyncScript ( this: DevToolsDriver, { script, args }: { script: string, args: any[] } diff --git a/packages/devtools/src/commands/executeScript.ts b/packages/devtools/src/commands/executeScript.ts index 00a00b6e7d7..a1eb236c644 100644 --- a/packages/devtools/src/commands/executeScript.ts +++ b/packages/devtools/src/commands/executeScript.ts @@ -1,3 +1,8 @@ +import command from '../scripts/executeScript' +import { transformExecuteArgs, transformExecuteResult } from '../utils' +import { SERIALIZE_PROPERTY, SERIALIZE_FLAG } from '../constants' +import type DevToolsDriver from '../devtoolsdriver' + /** * The Execute Script command executes a JavaScript function in the context of the * current browsing context and returns the return value of the function. @@ -8,12 +13,6 @@ * @param {*[]} args an array of JSON values which will be deserialized and passed as arguments to your function * @return * Either the return value of your script, the fulfillment of the Promise returned by your script, or the error which was the reason for your script's returned Promise's rejection. */ - -import command from '../scripts/executeScript' -import { transformExecuteArgs, transformExecuteResult } from '../utils' -import { SERIALIZE_PROPERTY, SERIALIZE_FLAG } from '../constants' -import type DevToolsDriver from '../devtoolsdriver' - export default async function executeScript ( this: DevToolsDriver, { script, args }: { script: string, args: any[] } diff --git a/packages/devtools/src/commands/findElement.ts b/packages/devtools/src/commands/findElement.ts index 74c272378d5..2dec54aaea5 100644 --- a/packages/devtools/src/commands/findElement.ts +++ b/packages/devtools/src/commands/findElement.ts @@ -1,3 +1,7 @@ +import { SUPPORTED_SELECTOR_STRATEGIES } from '../constants' +import { findElement as findElementUtil } from '../utils' +import type DevToolsDriver from '../devtoolsdriver' + /** * The Find Element command is used to find an element in the current browsing context * that can be used for future commands. @@ -8,11 +12,6 @@ * @param {string} value the actual selector that will be used to find an element * @return {Object} A JSON representation of an element object. */ - -import { SUPPORTED_SELECTOR_STRATEGIES } from '../constants' -import { findElement as findElementUtil } from '../utils' -import type DevToolsDriver from '../devtoolsdriver' - export default function findElement ( this: DevToolsDriver, { using, value }: { using: string, value: string } diff --git a/packages/devtools/src/commands/findElementFromElement.ts b/packages/devtools/src/commands/findElementFromElement.ts index d6653c6f998..95060392ff0 100644 --- a/packages/devtools/src/commands/findElementFromElement.ts +++ b/packages/devtools/src/commands/findElementFromElement.ts @@ -1,3 +1,7 @@ +import { SUPPORTED_SELECTOR_STRATEGIES } from '../constants' +import { findElement, getStaleElementError } from '../utils' +import type DevToolsDriver from '../devtoolsdriver' + /** * The Find Element From Element command is used to find an element from a web element * in the current browsing context that can be used for future commands. @@ -8,11 +12,6 @@ * @param {string} value the actual selector that will be used to find an element * @return {Object} A JSON representation of an element object. */ - -import { SUPPORTED_SELECTOR_STRATEGIES } from '../constants' -import { findElement, getStaleElementError } from '../utils' -import type DevToolsDriver from '../devtoolsdriver' - export default async function findElementFromElement ( this: DevToolsDriver, { elementId, using, value }: { elementId: string, using: string, value: string } diff --git a/packages/devtools/src/commands/findElements.ts b/packages/devtools/src/commands/findElements.ts index 3c9f1ae66b7..ac9eb6cebc5 100644 --- a/packages/devtools/src/commands/findElements.ts +++ b/packages/devtools/src/commands/findElements.ts @@ -1,3 +1,7 @@ +import { SUPPORTED_SELECTOR_STRATEGIES } from '../constants' +import { findElements as findElementsUtil } from '../utils' +import type DevToolsDriver from '../devtoolsdriver' + /** * The Find Elements command is used to find elements * in the current browsing context that can be used for future commands. @@ -8,11 +12,6 @@ * @param {string} value the actual selector that will be used to find an element * @return {object[]} A (possibly empty) JSON list of representations of an element object. */ - -import { SUPPORTED_SELECTOR_STRATEGIES } from '../constants' -import { findElements as findElementsUtil } from '../utils' -import type DevToolsDriver from '../devtoolsdriver' - export default async function findElements ( this: DevToolsDriver, { using, value }: { using: string, value: string } diff --git a/packages/devtools/src/commands/findElementsFromElement.ts b/packages/devtools/src/commands/findElementsFromElement.ts index 87b351625a4..1a385499e3b 100644 --- a/packages/devtools/src/commands/findElementsFromElement.ts +++ b/packages/devtools/src/commands/findElementsFromElement.ts @@ -1,3 +1,7 @@ +import { SUPPORTED_SELECTOR_STRATEGIES } from '../constants' +import { findElements, getStaleElementError } from '../utils' +import type DevToolsDriver from '../devtoolsdriver' + /** * The Find Elements From Element command is used to find elements from a web element * in the current browsing context that can be used for future commands. @@ -8,11 +12,6 @@ * @param {string} value the actual selector that will be used to find an element * @return {object[]} A (possibly empty) JSON list of representations of an element object. */ - -import { SUPPORTED_SELECTOR_STRATEGIES } from '../constants' -import { findElements, getStaleElementError } from '../utils' -import type DevToolsDriver from '../devtoolsdriver' - export default async function findElementFromElements ( this: DevToolsDriver, { elementId, using, value }: { elementId: string, using: string, value: string } diff --git a/packages/devtools/src/commands/forward.ts b/packages/devtools/src/commands/forward.ts index 4143c10646e..2111a131ab7 100644 --- a/packages/devtools/src/commands/forward.ts +++ b/packages/devtools/src/commands/forward.ts @@ -1,3 +1,5 @@ +import type DevToolsDriver from '../devtoolsdriver' + /** * The Forward command causes the browser to traverse one step forwards * in the joint session history of the current top-level browsing context. @@ -5,8 +7,6 @@ * @alias browser.forward * @see https://w3c.github.io/webdriver/#dfn-forward */ -import type DevToolsDriver from '../devtoolsdriver' - export default async function forward (this: DevToolsDriver) { delete this.currentFrame const page = this.getPageHandle() diff --git a/packages/devtools/src/commands/getActiveElement.ts b/packages/devtools/src/commands/getActiveElement.ts index 2b7f2efac2f..f950f6ae19f 100644 --- a/packages/devtools/src/commands/getActiveElement.ts +++ b/packages/devtools/src/commands/getActiveElement.ts @@ -1,3 +1,9 @@ +import findElement from './findElement' +import command from '../scripts/getActiveElement' +import cleanUp from '../scripts/cleanUpSerializationSelector' +import { SERIALIZE_PROPERTY } from '../constants' +import type DevToolsDriver from '../devtoolsdriver' + /** * Get Active Element returns the active element of the current browsing context’s document element. * @@ -5,13 +11,6 @@ * @see https://w3c.github.io/webdriver/#dfn-get-active-element * @return {Object} A JSON representation of an element object. */ - -import findElement from './findElement' -import command from '../scripts/getActiveElement' -import cleanUp from '../scripts/cleanUpSerializationSelector' -import { SERIALIZE_PROPERTY } from '../constants' -import type DevToolsDriver from '../devtoolsdriver' - export default async function getActiveElement ( this: DevToolsDriver ) { diff --git a/packages/devtools/src/commands/getAlertText.ts b/packages/devtools/src/commands/getAlertText.ts index 261f36c6941..1bb5df23545 100644 --- a/packages/devtools/src/commands/getAlertText.ts +++ b/packages/devtools/src/commands/getAlertText.ts @@ -1,3 +1,5 @@ +import type DevToolsDriver from '../devtoolsdriver' + /** * The Get Alert Text command returns the message of the current user prompt. * If there is no current user prompt, it returns an error. @@ -6,8 +8,6 @@ * @see https://w3c.github.io/webdriver/#dfn-get-alert-text * @return {string} The message of the user prompt. */ -import type DevToolsDriver from '../devtoolsdriver' - export default function getAlertText (this: DevToolsDriver) { if (!this.activeDialog) { throw new Error('no such alert') diff --git a/packages/devtools/src/commands/getAllCookies.ts b/packages/devtools/src/commands/getAllCookies.ts index 09b5556b8ca..daa28da846d 100644 --- a/packages/devtools/src/commands/getAllCookies.ts +++ b/packages/devtools/src/commands/getAllCookies.ts @@ -1,3 +1,5 @@ +import type DevToolsDriver from '../devtoolsdriver' + /** * The Get All Cookies command returns all cookies associated with the address * of the current browsing context’s active document. @@ -6,8 +8,6 @@ * @see https://w3c.github.io/webdriver/#dfn-get-all-cookies * @return {Object[]} A list of serialized cookies. Each serialized cookie has a number of optional fields which may or may not be returned in addition to `name` and `value`. */ -import type DevToolsDriver from '../devtoolsdriver' - export default async function getAllCookies (this: DevToolsDriver) { const page = this.getPageHandle() return page.cookies() diff --git a/packages/devtools/src/commands/getElementAttribute.ts b/packages/devtools/src/commands/getElementAttribute.ts index 6365882c9e3..58aea6d24c8 100644 --- a/packages/devtools/src/commands/getElementAttribute.ts +++ b/packages/devtools/src/commands/getElementAttribute.ts @@ -1,3 +1,7 @@ +import command from '../scripts/getElementAttribute' +import { getStaleElementError } from '../utils' +import type DevToolsDriver from '../devtoolsdriver' + /** * The Get Element Attribute command will return the attribute of a web element. * @@ -7,11 +11,6 @@ * @param {string} name name of the attribute value to retrieve * @return {string} The named attribute of the element. */ - -import command from '../scripts/getElementAttribute' -import { getStaleElementError } from '../utils' -import type DevToolsDriver from '../devtoolsdriver' - export default async function getElementAttribute ( this: DevToolsDriver, { elementId, name }: { elementId: string, name: string } diff --git a/packages/devtools/src/commands/getElementCSSValue.ts b/packages/devtools/src/commands/getElementCSSValue.ts index 95c533fbe2c..00eb8ef7dd6 100644 --- a/packages/devtools/src/commands/getElementCSSValue.ts +++ b/packages/devtools/src/commands/getElementCSSValue.ts @@ -1,3 +1,7 @@ +import command from '../scripts/getElementCSSValue' +import { getStaleElementError } from '../utils' +import type DevToolsDriver from '../devtoolsdriver' + /** * The Get Element CSS Value command retrieves the computed value * of the given CSS property of the given web element. @@ -8,11 +12,6 @@ * @param {string} propertyName name of the CSS property to retrieve * @return {string} The computed value of the parameter corresponding to property name from the element's style declarations (unless the document type is xml, in which case the return value is simply the empty string). */ - -import command from '../scripts/getElementCSSValue' -import { getStaleElementError } from '../utils' -import type DevToolsDriver from '../devtoolsdriver' - export default async function getElementCSSValue ( this: DevToolsDriver, { elementId, propertyName }: { elementId: string, propertyName: string } diff --git a/packages/devtools/src/commands/getElementComputedLabel.ts b/packages/devtools/src/commands/getElementComputedLabel.ts index 4cae2152e55..4e033e6ab1e 100644 --- a/packages/devtools/src/commands/getElementComputedLabel.ts +++ b/packages/devtools/src/commands/getElementComputedLabel.ts @@ -1,3 +1,5 @@ +import type DevToolsDriver from '../devtoolsdriver' + /** * Get the computed WAI-ARIA label of an element. * @@ -6,9 +8,6 @@ * @param {string} elementId the id of an element returned in a previous call to Find Element(s) * @return {string} The result of computing the WAI-ARIA label of element. */ - -import type DevToolsDriver from '../devtoolsdriver' - export default async function getElementComputedLabel ( this: DevToolsDriver, { elementId }: { elementId: string } diff --git a/packages/devtools/src/commands/getElementComputedRole.ts b/packages/devtools/src/commands/getElementComputedRole.ts index 389332f3c3b..001674676c4 100644 --- a/packages/devtools/src/commands/getElementComputedRole.ts +++ b/packages/devtools/src/commands/getElementComputedRole.ts @@ -1,3 +1,5 @@ +import type DevToolsDriver from '../devtoolsdriver' + /** * Get the computed WAI-ARIA role of an element. * @@ -6,9 +8,6 @@ * @param {string} elementId the id of an element returned in a previous call to Find Element(s) * @return {string} The result of computing the WAI-ARIA role of element. */ - -import type DevToolsDriver from '../devtoolsdriver' - export default async function getElementComputedRole ( this: DevToolsDriver, { elementId }: { elementId: string } diff --git a/packages/devtools/src/commands/getElementProperty.ts b/packages/devtools/src/commands/getElementProperty.ts index 1ce2a0bfe74..a953744f145 100644 --- a/packages/devtools/src/commands/getElementProperty.ts +++ b/packages/devtools/src/commands/getElementProperty.ts @@ -1,3 +1,6 @@ +import { getStaleElementError } from '../utils' +import type DevToolsDriver from '../devtoolsdriver' + /** * The Get Element Property command will return the result of getting a property of an element. * @@ -7,10 +10,6 @@ * @param {string} name name of the attribute property to retrieve * @return {string} The named property of the element, accessed by calling GetOwnProperty on the element object. */ - -import { getStaleElementError } from '../utils' -import type DevToolsDriver from '../devtoolsdriver' - export default async function getElementProperty ( this: DevToolsDriver, { elementId, name }: { elementId: string, name: string } diff --git a/packages/devtools/src/commands/getElementRect.ts b/packages/devtools/src/commands/getElementRect.ts index 8528525999e..b7b7b91a60b 100644 --- a/packages/devtools/src/commands/getElementRect.ts +++ b/packages/devtools/src/commands/getElementRect.ts @@ -1,3 +1,7 @@ +import command from '../scripts/getElementRect' +import { getStaleElementError } from '../utils' +import type DevToolsDriver from '../devtoolsdriver' + /** * The Get Element Rect command returns the dimensions and coordinates of the given web element. * @@ -6,11 +10,6 @@ * @param {string} elementId the id of an element returned in a previous call to Find Element(s) * @return {string} A JSON object representing the position and bounding rect of the element. */ - -import command from '../scripts/getElementRect' -import { getStaleElementError } from '../utils' -import type DevToolsDriver from '../devtoolsdriver' - export default async function getElementRect ( this: DevToolsDriver, { elementId }: { elementId: string } diff --git a/packages/devtools/src/commands/getElementTagName.ts b/packages/devtools/src/commands/getElementTagName.ts index 076cdeb327f..2aa7dfed5e5 100644 --- a/packages/devtools/src/commands/getElementTagName.ts +++ b/packages/devtools/src/commands/getElementTagName.ts @@ -1,3 +1,7 @@ +import command from '../scripts/getElementTagName' +import { getStaleElementError } from '../utils' +import type DevToolsDriver from '../devtoolsdriver' + /** * The Get Element Tag Name command returns the qualified element name of the given web element. * @@ -6,11 +10,6 @@ * @param {string} elementId the id of an element returned in a previous call to Find Element(s) * @return {string} The tagName attribute of the element. */ - -import command from '../scripts/getElementTagName' -import { getStaleElementError } from '../utils' -import type DevToolsDriver from '../devtoolsdriver' - export default async function getElementTagName ( this: DevToolsDriver, { elementId }: { elementId: string } diff --git a/packages/devtools/src/commands/getElementText.ts b/packages/devtools/src/commands/getElementText.ts index 5040d470692..9890df218ff 100644 --- a/packages/devtools/src/commands/getElementText.ts +++ b/packages/devtools/src/commands/getElementText.ts @@ -1,3 +1,7 @@ +import command from '../scripts/getElementText' +import { getStaleElementError } from '../utils' +import type DevToolsDriver from '../devtoolsdriver' + /** * The Get Element Text command intends to return an element’s text \"as rendered\". * An element's rendered text is also used for locating a elements @@ -8,11 +12,6 @@ * @param {string} elementId the id of an element returned in a previous call to Find Element(s) * @return {string} The visible text of the element (including child elements). */ - -import command from '../scripts/getElementText' -import { getStaleElementError } from '../utils' -import type DevToolsDriver from '../devtoolsdriver' - export default async function getElementText ( this: DevToolsDriver, { elementId }: { elementId: string } diff --git a/packages/devtools/src/commands/getNamedCookie.ts b/packages/devtools/src/commands/getNamedCookie.ts index 0b97d5068ee..c971a3f9f58 100644 --- a/packages/devtools/src/commands/getNamedCookie.ts +++ b/packages/devtools/src/commands/getNamedCookie.ts @@ -1,3 +1,5 @@ +import type DevToolsDriver from '../devtoolsdriver' + /** * The Get Named Cookie command returns the cookie with the requested name from the * associated cookies in the cookie store of the current browsing context's active document. @@ -8,8 +10,6 @@ * @param {string} name name of the cookie to retrieve * @return {object} A serialized cookie, with name and value fields. There are a number of optional fields like `path`, `domain`, and `expiry-time` which may also be present. */ -import type DevToolsDriver from '../devtoolsdriver' - export default async function getNamedCookie ( this: DevToolsDriver, { name }: { name: string } diff --git a/packages/devtools/src/commands/getPageSource.ts b/packages/devtools/src/commands/getPageSource.ts index 880734df9ff..b7e3e038d41 100644 --- a/packages/devtools/src/commands/getPageSource.ts +++ b/packages/devtools/src/commands/getPageSource.ts @@ -1,3 +1,5 @@ +import type DevToolsDriver from '../devtoolsdriver' + /** * The Get Page Source command returns a string serialization of the DOM * of the current browsing context active document. @@ -6,8 +8,6 @@ * @alias browser.getPageSource * @return {string} the DOM of the current browsing context active document */ -import type DevToolsDriver from '../devtoolsdriver' - export default function getPageSource (this: DevToolsDriver) { const page = this.getPageHandle(true) return page.content() diff --git a/packages/devtools/src/commands/getTimeouts.ts b/packages/devtools/src/commands/getTimeouts.ts index 72e0702aadf..872db7a3503 100644 --- a/packages/devtools/src/commands/getTimeouts.ts +++ b/packages/devtools/src/commands/getTimeouts.ts @@ -1,3 +1,5 @@ +import type DevToolsDriver from '../devtoolsdriver' + /** * The Get Timeouts command gets timeout durations associated with the current session. * @@ -5,8 +7,6 @@ * @see https://w3c.github.io/webdriver/#dfn-get-timeouts * @return {Object} Object containing timeout durations for `script`, `pageLoad` and `implicit` timeouts. */ -import type DevToolsDriver from '../devtoolsdriver' - export default function getTimeouts (this: DevToolsDriver) { return { implicit: this.timeouts.get('implicit'), diff --git a/packages/devtools/src/commands/getTitle.ts b/packages/devtools/src/commands/getTitle.ts index fd07215dd5d..f9c314709cf 100644 --- a/packages/devtools/src/commands/getTitle.ts +++ b/packages/devtools/src/commands/getTitle.ts @@ -1,3 +1,5 @@ +import type DevToolsDriver from '../devtoolsdriver' + /** * The Get Title command returns the document title of the current top-level * browsing context, equivalent to calling `document.title`. @@ -6,8 +8,6 @@ * @see https://w3c.github.io/webdriver/#dfn-get-title * @return {string} Returns a string which is the same as `document.title` of the current top-level browsing context. */ -import type DevToolsDriver from '../devtoolsdriver' - export default async function getTitle (this: DevToolsDriver) { const page = this.getPageHandle(true) return page.title() diff --git a/packages/devtools/src/commands/getUrl.ts b/packages/devtools/src/commands/getUrl.ts index ee800a3514b..e845672363a 100644 --- a/packages/devtools/src/commands/getUrl.ts +++ b/packages/devtools/src/commands/getUrl.ts @@ -1,3 +1,6 @@ +import command from '../scripts/getUrl' +import type DevToolsDriver from '../devtoolsdriver' + /** * The Get Current URL command returns the URL of the current top-level browsing context * @@ -5,10 +8,6 @@ * @see https://w3c.github.io/webdriver/#dfn-get-current-url * @returns {String} current document URL of the top-level browsing context. */ - -import command from '../scripts/getUrl' -import type DevToolsDriver from '../devtoolsdriver' - export default async function getUrl (this: DevToolsDriver) { const page = this.getPageHandle(true) return page.$eval('html', command) diff --git a/packages/devtools/src/commands/getWindowHandle.ts b/packages/devtools/src/commands/getWindowHandle.ts index 718bc7974a3..31c306b3125 100644 --- a/packages/devtools/src/commands/getWindowHandle.ts +++ b/packages/devtools/src/commands/getWindowHandle.ts @@ -1,3 +1,5 @@ +import type DevToolsDriver from '../devtoolsdriver' + /** * The Get Window Handle command returns the window handle for the current top-level browsing context. * It can be used as an argument to Switch To Window. @@ -6,8 +8,6 @@ * @see https://w3c.github.io/webdriver/#dfn-get-window-handle * @return {string} Returns a string which is the window handle for the current top-level browsing context. */ -import type DevToolsDriver from '../devtoolsdriver' - export default async function getWindowHandle (this: DevToolsDriver) { return this.currentWindowHandle } diff --git a/packages/devtools/src/commands/getWindowHandles.ts b/packages/devtools/src/commands/getWindowHandles.ts index cc85c412feb..86a2bd251de 100644 --- a/packages/devtools/src/commands/getWindowHandles.ts +++ b/packages/devtools/src/commands/getWindowHandles.ts @@ -1,3 +1,6 @@ +import { v4 as uuidv4 } from 'uuid' +import type DevToolsDriver from '../devtoolsdriver' + /** * The Get Window Handles command returns a list of window handles * for every open top-level browsing context. @@ -7,10 +10,6 @@ * @see https://w3c.github.io/webdriver/#dfn-get-window-handles * @return {string[]} An array which is a list of window handles. */ - -import { v4 as uuidv4 } from 'uuid' -import type DevToolsDriver from '../devtoolsdriver' - export default async function getWindowHandles(this: DevToolsDriver) { let newPages = await this.browser.pages() diff --git a/packages/devtools/src/commands/getWindowRect.ts b/packages/devtools/src/commands/getWindowRect.ts index 73c8ada1ffe..941d2352394 100644 --- a/packages/devtools/src/commands/getWindowRect.ts +++ b/packages/devtools/src/commands/getWindowRect.ts @@ -1,3 +1,6 @@ +import { DEFAULT_WIDTH, DEFAULT_HEIGHT } from '../constants' +import type DevToolsDriver from '../devtoolsdriver' + /** * The Get Window Rect command returns the size and position on the screen of the operating system window * corresponding to the current top-level browsing context. @@ -6,10 +9,6 @@ * @see https://w3c.github.io/webdriver/#dfn-get-window-rect * @return {object} A JSON representation of a "window rect" object. This has 4 properties: `x`, `y`, `width` and `height`. */ - -import { DEFAULT_WIDTH, DEFAULT_HEIGHT } from '../constants' -import type DevToolsDriver from '../devtoolsdriver' - export default async function getWindowRect (this: DevToolsDriver) { const page = this.getPageHandle() const viewport = await page.viewport() || {} diff --git a/packages/devtools/src/commands/isElementEnabled.ts b/packages/devtools/src/commands/isElementEnabled.ts index f282c188c95..3abd551bdd7 100644 --- a/packages/devtools/src/commands/isElementEnabled.ts +++ b/packages/devtools/src/commands/isElementEnabled.ts @@ -1,3 +1,6 @@ +import getElementAttribute from './getElementAttribute' +import type DevToolsDriver from '../devtoolsdriver' + /** * Is Element Enabled determines if the referenced element is enabled or not. * This operation only makes sense on form controls. @@ -7,10 +10,6 @@ * @param {string} elementId the id of an element returned in a previous call to Find Element(s) * @return {string} If the element is in an xml document, or is a disabled form control: `false`, otherwise, `true`. */ - -import getElementAttribute from './getElementAttribute' -import type DevToolsDriver from '../devtoolsdriver' - export default async function isElementEnabled ( this: DevToolsDriver, { elementId }: { elementId: string } diff --git a/packages/devtools/src/commands/isElementSelected.ts b/packages/devtools/src/commands/isElementSelected.ts index a517a533fdb..47e3871f9c2 100644 --- a/packages/devtools/src/commands/isElementSelected.ts +++ b/packages/devtools/src/commands/isElementSelected.ts @@ -1,3 +1,7 @@ +import getElementProperty from './getElementProperty' +import getElementTagName from './getElementTagName' +import type DevToolsDriver from '../devtoolsdriver' + /** * Is Element Selected determines if the referenced element is selected or not. * This operation only makes sense on input elements of the Checkbox- and Radio Button states, @@ -8,11 +12,6 @@ * @param {string} elementId the id of an element returned in a previous call to Find Element(s) * @return {boolean} `true` or `false` based on the selected state. */ - -import getElementProperty from './getElementProperty' -import getElementTagName from './getElementTagName' -import type DevToolsDriver from '../devtoolsdriver' - export default async function isElementSelected ( this: DevToolsDriver, { elementId }: { elementId: string } diff --git a/packages/devtools/src/commands/navigateTo.ts b/packages/devtools/src/commands/navigateTo.ts index 8b9945e34ac..dc559ac2712 100644 --- a/packages/devtools/src/commands/navigateTo.ts +++ b/packages/devtools/src/commands/navigateTo.ts @@ -1,3 +1,5 @@ +import type DevToolsDriver from '../devtoolsdriver' + /** * The navigateTo (go) command is used to cause the user agent to navigate the current * top-level browsing context a new location. @@ -7,8 +9,6 @@ * @param {string} url current top-level browsing context’s active document’s document URL * @return {string} current document URL of the top-level browsing context. */ -import type DevToolsDriver from '../devtoolsdriver' - export default async function navigateTo ( this: DevToolsDriver, { url }: { url: string } diff --git a/packages/devtools/src/commands/newSession.ts b/packages/devtools/src/commands/newSession.ts index 42b54ba038c..92467c7ceb1 100644 --- a/packages/devtools/src/commands/newSession.ts +++ b/packages/devtools/src/commands/newSession.ts @@ -1,3 +1,11 @@ +import os from 'os' +import { v4 as uuidv4 } from 'uuid' + +import launch from '../launcher' +import { sessionMap } from '../index' +import type { ExtendedCapabilities } from '../types' +import type DevToolsDriver from '../devtoolsdriver' + /** * The New Session command creates a new WebDriver session with the endpoint node. * If the creation fails, a session not created error is returned. @@ -7,17 +15,9 @@ * @param {Object} capabilities An object describing the set of capabilities for the capability processing algorithm * @return {Object} Object containing sessionId and capabilities of created WebDriver session. */ - -import os from 'os' -import { v4 as uuidv4 } from 'uuid' - -import launch from '../launcher' -import { sessionMap } from '../index' -import type DevToolsDriver from '../devtoolsdriver' - export default async function newSession ( this: DevToolsDriver, - { capabilities }: { capabilities: WebDriver.Capabilities } + { capabilities }: { capabilities: ExtendedCapabilities } ) { const browser = await launch(capabilities) const sessionId = uuidv4() diff --git a/packages/devtools/src/commands/performActions.ts b/packages/devtools/src/commands/performActions.ts index 404a50ec7c5..7688fb3d3b9 100644 --- a/packages/devtools/src/commands/performActions.ts +++ b/packages/devtools/src/commands/performActions.ts @@ -1,12 +1,3 @@ -/** - * The Perform Actions command is used to execute complex user actions. - * See [spec](https://github.com/jlipps/simple-wd-spec#perform-actions) for more details. - * - * @alias browser.performActions - * @see https://w3c.github.io/webdriver/#dfn-perform-actions - * @param {object[]} actions A list of objects, each of which represents an input source and its associated actions. - */ - import { keyDefinitions, KeyInput } from 'puppeteer-core/lib/cjs/puppeteer/common/USKeyboardLayout' import type { Keyboard, Mouse } from 'puppeteer-core/lib/cjs/puppeteer/common/Input' @@ -38,6 +29,14 @@ interface ActionsParameter { } } +/** + * The Perform Actions command is used to execute complex user actions. + * See [spec](https://github.com/jlipps/simple-wd-spec#perform-actions) for more details. + * + * @alias browser.performActions + * @see https://w3c.github.io/webdriver/#dfn-perform-actions + * @param {object[]} actions A list of objects, each of which represents an input source and its associated actions. + */ export default async function performActions( this: DevToolsDriver, { actions }: { actions: ActionsParameter[] } diff --git a/packages/devtools/src/commands/refresh.ts b/packages/devtools/src/commands/refresh.ts index d213984990a..8630d800b17 100644 --- a/packages/devtools/src/commands/refresh.ts +++ b/packages/devtools/src/commands/refresh.ts @@ -1,11 +1,11 @@ +import type DevToolsDriver from '../devtoolsdriver' + /** * The Refresh command causes the browser to reload the page in current top-level browsing context. * * @alias browser.refresh * @see https://w3c.github.io/webdriver/#dfn-refresh */ -import type DevToolsDriver from '../devtoolsdriver' - export default async function refresh (this: DevToolsDriver) { delete this.currentFrame diff --git a/packages/devtools/src/commands/releaseActions.ts b/packages/devtools/src/commands/releaseActions.ts index c6b691da654..279034cbca7 100644 --- a/packages/devtools/src/commands/releaseActions.ts +++ b/packages/devtools/src/commands/releaseActions.ts @@ -1,15 +1,11 @@ /** * The Release Actions command is used to release all the keys and pointer buttons that are currently depressed. * This causes events to be fired as if the state was released by an explicit series of actions. - * It also clears all the internal state of the virtual devices." + * It also clears all the internal state of the virtual devices. + * + * There is no reference implementation for it in Puppeteer so this command will be a NOOP. * * @alias browser.performActions * @see https://w3c.github.io/webdriver/#dfn-release-actions */ - -/** - * there is no reference implementation for it in Puppeteer - */ -export default async function performActions () { - // NYI -} +export default async function performActions () {} diff --git a/packages/devtools/src/commands/sendAlertText.ts b/packages/devtools/src/commands/sendAlertText.ts index 93d35bc811b..455c6f46bbb 100644 --- a/packages/devtools/src/commands/sendAlertText.ts +++ b/packages/devtools/src/commands/sendAlertText.ts @@ -1,3 +1,5 @@ +import type DevToolsDriver from '../devtoolsdriver' + /** * The Send Alert Text command sets the text field of a window.prompt user prompt to the given value. * @@ -5,8 +7,6 @@ * @see https://w3c.github.io/webdriver/#dfn-send-alert-text * @param {string} text string to set the prompt to */ -import type DevToolsDriver from '../devtoolsdriver' - export default async function sendAlertText ( this: DevToolsDriver, { text }: { text: string }) { diff --git a/packages/devtools/src/commands/setTimeouts.ts b/packages/devtools/src/commands/setTimeouts.ts index 88882dcb620..3260ce81d7a 100644 --- a/packages/devtools/src/commands/setTimeouts.ts +++ b/packages/devtools/src/commands/setTimeouts.ts @@ -1,3 +1,5 @@ +import type DevToolsDriver from '../devtoolsdriver' + /** * The Set Timeouts command sets timeout durations associated with the current session. * The timeouts that can be controlled are listed in the table of session timeouts below. @@ -8,8 +10,6 @@ * @param {number} pageLoad integer in ms for session page load timeout * @param {number} script integer in ms for session script timeout */ -import type DevToolsDriver from '../devtoolsdriver' - export default async function setTimeouts ( this: DevToolsDriver, { implicit, pageLoad, script }: { implicit: number, pageLoad: number, script: number } diff --git a/packages/devtools/src/commands/setWindowRect.ts b/packages/devtools/src/commands/setWindowRect.ts index b425dc02510..582b0588fe0 100644 --- a/packages/devtools/src/commands/setWindowRect.ts +++ b/packages/devtools/src/commands/setWindowRect.ts @@ -1,3 +1,5 @@ +import type DevToolsDriver from '../devtoolsdriver' + /** * The Set Window Rect command alters the size and the position of the operating system window * corresponding to the current top-level browsing context. @@ -10,8 +12,6 @@ * @param {number} height the height of the outer dimensions of the top-level browsing context, including browser chrome etc... * @return {object} A JSON representation of a "window rect" object based on the new window state. */ -import type DevToolsDriver from '../devtoolsdriver' - export default async function setWindowRect ( this: DevToolsDriver, params: { width: number, height: number } diff --git a/packages/devtools/src/commands/status.ts b/packages/devtools/src/commands/status.ts index d19d0650ac1..278b064121b 100644 --- a/packages/devtools/src/commands/status.ts +++ b/packages/devtools/src/commands/status.ts @@ -1,3 +1,8 @@ +import path from 'path' + +const puppeteerPath = require.resolve('puppeteer-core') +const puppeteerPkg = require(`${path.dirname(puppeteerPath)}/package.json`) + /** * The Status command returns information about whether a remote end is in a state * in which it can create new sessions and can additionally include arbitrary meta information @@ -7,12 +12,6 @@ * @see https://w3c.github.io/webdriver/#dfn-status * @return {Object} returning an object with the Puppeteer version being used */ - -import path from 'path' - -const puppeteerPath = require.resolve('puppeteer-core') -const puppeteerPkg = require(`${path.dirname(puppeteerPath)}/package.json`) - export default async function status () { return { message: '', diff --git a/packages/devtools/src/commands/switchToFrame.ts b/packages/devtools/src/commands/switchToFrame.ts index b2c422c7928..399369d0bb5 100644 --- a/packages/devtools/src/commands/switchToFrame.ts +++ b/packages/devtools/src/commands/switchToFrame.ts @@ -1,3 +1,11 @@ +import type { Page } from 'puppeteer-core/lib/cjs/puppeteer/common/Page' +import type { Frame } from 'puppeteer-core/lib/cjs/puppeteer/common/FrameManager' +import type { ElementReference } from '@wdio/protocols' + +import { ELEMENT_KEY } from '../constants' +import { getStaleElementError } from '../utils' +import type DevToolsDriver from '../devtoolsdriver' + /** * The Switch To Frame command is used to select the current top-level browsing context * or a child browsing context of the current browsing context to use as the current @@ -7,13 +15,6 @@ * @see https://w3c.github.io/webdriver/#dfn-switch-to-frame * @param {string|object|null} id one of three possible types: null: this represents the top-level browsing context (i.e., not an iframe), a Number, representing the index of the window object corresponding to a frame, an Element object received using `findElement`. */ -import type { Page } from 'puppeteer-core/lib/cjs/puppeteer/common/Page' -import type { Frame } from 'puppeteer-core/lib/cjs/puppeteer/common/FrameManager' - -import { ELEMENT_KEY } from '../constants' -import { getStaleElementError } from '../utils' -import type DevToolsDriver from '../devtoolsdriver' - export default async function switchToFrame ( this: DevToolsDriver, { id }: { id: string } @@ -35,7 +36,7 @@ export default async function switchToFrame ( /** * switch frame by element ID */ - const idAsElementReference = id as unknown as WebDriver.ElementReference + const idAsElementReference = id as unknown as ElementReference if (typeof idAsElementReference[ELEMENT_KEY] === 'string') { const elementHandle = await this.elementStore.get(idAsElementReference[ELEMENT_KEY]) diff --git a/packages/devtools/src/commands/switchToParentFrame.ts b/packages/devtools/src/commands/switchToParentFrame.ts index 6c0b448ad0f..e96df4c48f1 100644 --- a/packages/devtools/src/commands/switchToParentFrame.ts +++ b/packages/devtools/src/commands/switchToParentFrame.ts @@ -1,3 +1,7 @@ +import type { Page } from 'puppeteer-core/lib/cjs/puppeteer/common/Page' +import type { Frame } from 'puppeteer-core/lib/cjs/puppeteer/common/FrameManager' +import type DevToolsDriver from '../devtoolsdriver' + /** * The Switch to Parent Frame command sets the current browsing context for future commands * to the parent of the current browsing context. @@ -5,10 +9,6 @@ * @alias browser.switchToParentFrame * @see https://w3c.github.io/webdriver/#dfn-switch-to-parent-frame */ -import type { Page } from 'puppeteer-core/lib/cjs/puppeteer/common/Page' -import type { Frame } from 'puppeteer-core/lib/cjs/puppeteer/common/FrameManager' -import type DevToolsDriver from '../devtoolsdriver' - export default async function switchToParentFrame (this: DevToolsDriver) { const page = this.getPageHandle(true) as unknown as Frame diff --git a/packages/devtools/src/commands/switchToWindow.ts b/packages/devtools/src/commands/switchToWindow.ts index 68db153c500..51bc061c746 100644 --- a/packages/devtools/src/commands/switchToWindow.ts +++ b/packages/devtools/src/commands/switchToWindow.ts @@ -1,3 +1,5 @@ +import type DevToolsDriver from '../devtoolsdriver' + /** * The Switch To Window command is used to select the current top-level browsing context * for the current session, i.e. the one that will be used for processing commands. @@ -6,8 +8,6 @@ * @see https://w3c.github.io/webdriver/#dfn-switch-to-window * @param {string} handle representing a window handle, should be one of the strings that was returned in a call to getWindowHandles */ -import type DevToolsDriver from '../devtoolsdriver' - export default async function switchToWindow ( this: DevToolsDriver, { handle }: { handle: string } diff --git a/packages/devtools/src/commands/takeElementScreenshot.ts b/packages/devtools/src/commands/takeElementScreenshot.ts index 8cf8091f31f..7c98144fca4 100644 --- a/packages/devtools/src/commands/takeElementScreenshot.ts +++ b/packages/devtools/src/commands/takeElementScreenshot.ts @@ -1,3 +1,6 @@ +import { getStaleElementError } from '../utils' +import type DevToolsDriver from '../devtoolsdriver' + /** * The Take Element Screenshot command takes a screenshot of the visible region * encompassed by the bounding rectangle of an element. @@ -7,10 +10,6 @@ * @param {string} elementId the id of an element returned in a previous call to Find Element(s) * @return {string} The base64-encoded PNG image data comprising the screenshot of the visible region of an element’s bounding rectangle after it has been scrolled into view. */ - -import { getStaleElementError } from '../utils' -import type DevToolsDriver from '../devtoolsdriver' - export default async function takeElementScreenshot ( this: DevToolsDriver, { elementId }: { elementId: string } diff --git a/packages/devtools/src/commands/takeScreenshot.ts b/packages/devtools/src/commands/takeScreenshot.ts index 22c43d4fef8..c741172837b 100644 --- a/packages/devtools/src/commands/takeScreenshot.ts +++ b/packages/devtools/src/commands/takeScreenshot.ts @@ -1,3 +1,5 @@ +import type DevToolsDriver from '../devtoolsdriver' + /** * The Take Screenshot command takes a screenshot of the top-level browsing context's viewport. * @@ -5,8 +7,6 @@ * @see https://w3c.github.io/webdriver/#dfn-take-screenshot * @return {string} The base64-encoded PNG image data comprising the screenshot of the initial viewport. */ -import type DevToolsDriver from '../devtoolsdriver' - export default async function takeScreenshot (this: DevToolsDriver) { const page = this.getPageHandle() return page.screenshot({ diff --git a/packages/devtools/src/constants.ts b/packages/devtools/src/constants.ts index beb84aaaa08..e17b97ea0e9 100644 --- a/packages/devtools/src/constants.ts +++ b/packages/devtools/src/constants.ts @@ -1,4 +1,4 @@ -import type { DefaultOptions } from '@wdio/config' +import type { Options } from '@wdio/types' export const DEFAULT_WIDTH = 1200 export const DEFAULT_HEIGHT = 900 @@ -66,7 +66,7 @@ export const BROWSER_TYPE: { edge: 'edge' } -export const DEFAULTS: DefaultOptions = { +export const DEFAULTS: Options.Definition = { capabilities: { type: 'object', required: true diff --git a/packages/devtools/src/devtoolsdriver.ts b/packages/devtools/src/devtoolsdriver.ts index 4a973d429e8..baec2f02320 100644 --- a/packages/devtools/src/devtoolsdriver.ts +++ b/packages/devtools/src/devtoolsdriver.ts @@ -6,7 +6,7 @@ import logger from '@wdio/logger' import type { Browser } from 'puppeteer-core/lib/cjs/puppeteer/common/Browser' import type { Dialog } from 'puppeteer-core/lib/cjs/puppeteer/common/Dialog' import type { Page } from 'puppeteer-core/lib/cjs/puppeteer/common/Page' -import type WDIOProtocols from '@wdio/protocols' +import type { CommandEndpoint } from '@wdio/protocols' import ElementStore from './elementstore' import { validate, sanitizeError } from './utils' @@ -78,7 +78,7 @@ export default class DevToolsDriver { return require(filePath).default } - register(commandInfo: WDIOProtocols.CommandEndpoint) { + register(commandInfo: CommandEndpoint) { const self = this const { command, ref, parameters, variables = [] } = commandInfo @@ -93,7 +93,7 @@ export default class DevToolsDriver { * within here you find the webdriver scope */ let retries = 0 - const wrappedCommand = async function (this: WebdriverIO.BrowserObject, ...args: any[]): Promise { + const wrappedCommand = async function (this: Browser, ...args: any[]): Promise { await self.checkPendingNavigations() const params = validate(command, parameters, variables as any, ref, args) let result diff --git a/packages/devtools/src/index.ts b/packages/devtools/src/index.ts index 3ac9fd345f4..2ae0aad5feb 100644 --- a/packages/devtools/src/index.ts +++ b/packages/devtools/src/index.ts @@ -6,12 +6,15 @@ import { v4 as uuidv4 } from 'uuid' import logger from '@wdio/logger' import { webdriverMonad, devtoolsEnvironmentDetector } from '@wdio/utils' import { validateConfig } from '@wdio/config' +import type { CommandEndpoint } from '@wdio/protocols' +import type { Options, Capabilities } from '@wdio/types' import type { Browser } from 'puppeteer-core/lib/cjs/puppeteer/common/Browser' import DevToolsDriver from './devtoolsdriver' import launch from './launcher' import { DEFAULTS, SUPPORTED_BROWSER, VENDOR_PREFIX } from './constants' import { getPrototype, patchDebug } from './utils' +import type { ExtendedCapabilities } from './types' const log = logger('devtools:puppeteer') @@ -23,7 +26,7 @@ patchDebug(log) export const sessionMap = new Map() export default class DevTools { - static async newSession (options: WebDriver.Options = {}, modifier?: Function, userPrototype = {}, customCommandWrapper?: Function) { + static async newSession (options: Options.WebDriver, modifier?: Function, userPrototype = {}, customCommandWrapper?: Function) { const params = validateConfig(DEFAULTS, options) if (params.logLevel && (!options.logLevels || !(options.logLevels as any)['devtools'])) { @@ -39,7 +42,8 @@ export default class DevTools { log.info('Initiate new session using the DevTools protocol') - const browser = await launch(params.capabilities as WebDriver.Capabilities) + const requestedCapabilities = { ...params.capabilities } + const browser = await launch(params.capabilities as ExtendedCapabilities) const pages = await browser.pages() const driver = new DevToolsDriver(browser, pages) const sessionId = uuidv4() @@ -51,16 +55,10 @@ export default class DevTools { */ type ValueOf = T[keyof T] const availableVendorPrefixes = Object.values(VENDOR_PREFIX) - const vendorCapPrefix = Object.keys(params.capabilities as WebDriver.Capabilities) + const vendorCapPrefix = Object.keys(params.capabilities as Capabilities.Capabilities) .find( (capKey: ValueOf) => availableVendorPrefixes.includes(capKey) - ) as keyof WebDriver.Capabilities - - /** - * save original set of capabilities to allow to request the same session again - * (e.g. for reloadSession command in WebdriverIO) - */ - params.requestedCapabilities = { ...params.capabilities } + ) as keyof Capabilities.Capabilities params.capabilities = { browserName: userAgent.browser.name, @@ -88,7 +86,7 @@ export default class DevTools { const commandWrapper = ( method: string, endpoint: string, - commandInfo: WDIOProtocols.CommandEndpoint + commandInfo: CommandEndpoint ) => driver.register(commandInfo) const protocolCommands = getPrototype(commandWrapper) const prototype = { @@ -97,11 +95,15 @@ export default class DevTools { ...environmentPrototype } - const monad = webdriverMonad(params, modifier, prototype) + const monad = webdriverMonad( + { ...params, requestedCapabilities }, + modifier, + prototype + ) return monad(sessionId, customCommandWrapper) } - static async reloadSession (instance: WebdriverIO.BrowserObject) { + static async reloadSession (instance: any) { const { session } = sessionMap.get(instance.sessionId) const browser = await launch(instance.requestedCapabilities) const pages = await browser.pages() diff --git a/packages/devtools/src/launcher.ts b/packages/devtools/src/launcher.ts index c815145e04f..7b0eb144941 100644 --- a/packages/devtools/src/launcher.ts +++ b/packages/devtools/src/launcher.ts @@ -2,6 +2,7 @@ import { launch as launchChromeBrowser } from 'chrome-launcher' import puppeteer from 'puppeteer-core' import logger from '@wdio/logger' import type { Browser } from 'puppeteer-core/lib/cjs/puppeteer/common/Browser' +import type { Capabilities } from '@wdio/types' import browserFinder from './finder' import { getPages } from './utils' @@ -20,31 +21,19 @@ import { CHANNEL_FIREFOX_TRUNK, BROWSER_ERROR_MESSAGES } from './constants' +import type { ExtendedCapabilities } from './types' const log = logger('devtools') const DEVICE_NAMES = Object.values(puppeteer.devices).map((device) => device.name) -interface DevToolsOptions { - ignoreDefaultArgs?: string[] | boolean - headless?: boolean, - defaultViewport?: { - width: number, - height: number - } -} - -interface ExtendedCapabilities extends WebDriver.Capabilities { - 'wdio:devtoolsOptions'?: DevToolsOptions -} - /** * launches Chrome and returns a Puppeteer browser instance * @param {object} capabilities session capabilities * @return {object} puppeteer browser instance */ async function launchChrome (capabilities: ExtendedCapabilities) { - const chromeOptions: WebDriver.ChromeOptions = capabilities[VENDOR_PREFIX.chrome] || {} + const chromeOptions: Capabilities.ChromeOptions = capabilities[VENDOR_PREFIX.chrome] || {} const mobileEmulation = chromeOptions.mobileEmulation || {} const devtoolsOptions = capabilities['wdio:devtoolsOptions'] diff --git a/packages/devtools/src/types.ts b/packages/devtools/src/types.ts new file mode 100644 index 00000000000..39a9c6a9378 --- /dev/null +++ b/packages/devtools/src/types.ts @@ -0,0 +1,14 @@ +import type { Capabilities } from '@wdio/types' + +export interface ExtendedCapabilities extends Capabilities.Capabilities { + 'wdio:devtoolsOptions'?: DevToolsOptions +} + +export interface DevToolsOptions { + ignoreDefaultArgs?: string[] | boolean + headless?: boolean, + defaultViewport?: { + width: number, + height: number + } +} diff --git a/packages/devtools/src/utils.ts b/packages/devtools/src/utils.ts index 5c082d3f0e6..086c78b74d8 100644 --- a/packages/devtools/src/utils.ts +++ b/packages/devtools/src/utils.ts @@ -3,7 +3,7 @@ import path from 'path' import { execFileSync } from 'child_process' import logger from '@wdio/logger' import { commandCallStructure, isValidParameter, getArgumentType, canAccess } from '@wdio/utils' -import { WebDriverProtocol } from '@wdio/protocols' +import { WebDriverProtocol, CommandParameters, CommandPathVariables, ElementReference } from '@wdio/protocols' import type { Logger } from '@wdio/logger' import type { ElementHandle } from 'puppeteer-core/lib/cjs/puppeteer/common/JSHandle' import type { Browser } from 'puppeteer-core/lib/cjs/puppeteer/common/Browser' @@ -19,8 +19,8 @@ const log = logger('devtools') export const validate = function ( command: string, - parameters: WDIOProtocols.CommandParameters[], - variables: WDIOProtocols.CommandPathVariables[], + parameters: CommandParameters[], + variables: CommandPathVariables[], ref: string, args: any[] ) { @@ -101,7 +101,7 @@ export async function findElement ( this: DevToolsDriver, context: Frame | Page | ElementHandle, using: string, value: string -): Promise { +): Promise { /** * implicitly wait for the element if timeout is set */ @@ -141,7 +141,7 @@ export async function findElements ( this: DevToolsDriver, context: Page | Frame | ElementHandle, using: string, value: string -): Promise { +): Promise { /** * implicitly wait for the element if timeout is set */ diff --git a/packages/devtools/tests/__snapshots__/devtools.test.ts.snap b/packages/devtools/tests/__snapshots__/devtools.test.ts.snap index 02346beda1d..8822cc2aaf2 100644 --- a/packages/devtools/tests/__snapshots__/devtools.test.ts.snap +++ b/packages/devtools/tests/__snapshots__/devtools.test.ts.snap @@ -13,6 +13,5 @@ Object { exports[`newSession 2`] = ` Object { "browserName": "chrome", - "goog:chromeOptions": Object {}, } `; diff --git a/packages/devtools/tsconfig.json b/packages/devtools/tsconfig.json index 4b6096725a5..636f019946c 100644 --- a/packages/devtools/tsconfig.json +++ b/packages/devtools/tsconfig.json @@ -4,9 +4,10 @@ "baseUrl": ".", "outDir": "./build", "rootDir": "./src", - "types": ["webdriverio", "@wdio/protocols"], + "types": ["@wdio/protocols"], }, "include": [ - "src/**/*" + "src/**/*", + "../../@types" ] } diff --git a/packages/devtools/tsconfig.prod.json b/packages/devtools/tsconfig.prod.json index 0ab273002e7..1c6d8bb2250 100644 --- a/packages/devtools/tsconfig.prod.json +++ b/packages/devtools/tsconfig.prod.json @@ -7,6 +7,7 @@ "types": ["webdriverio", "@wdio/protocols"] }, "include": [ - "src/**/*" + "src/**/*", + "../../@types" ] } diff --git a/packages/wdio-allure-reporter/allure-reporter.d.ts b/packages/wdio-allure-reporter/allure-reporter.d.ts deleted file mode 100644 index 2b866f7041b..00000000000 --- a/packages/wdio-allure-reporter/allure-reporter.d.ts +++ /dev/null @@ -1,55 +0,0 @@ -declare namespace AllureReporter { - type StepStatus = 'passed' | 'failed' | 'broken' | 'canceled' | 'skipped' - - function addFeature( - featureName: string - ): void; - function addLabel( - name: string, - value: string - ): void; - function addSeverity( - severity: string - ): void; - function addIssue( - issue: string - ): void; - function addTestId( - testId: string - ): void; - function addStory( - storyName: string - ): void; - function addEnvironment( - name: string, - value: string - ): void; - function addDescription( - description: string, - descriptionType: string - ): void; - function addAttachment( - name: string, - content: any, - mimeType?: string - ): void; - function startStep( - title: string - ): void; - function endStep( - status?: StepStatus - ): void; - function addStep( - title: string, - attachmentObject?: object, - status?: string - ): void; - function addArgument( - name: string, - value: string - ): void; -} - -declare module "@wdio/allure-reporter" { - export default AllureReporter -} diff --git a/packages/wdio-allure-reporter/package.json b/packages/wdio-allure-reporter/package.json index 37b7cf9ec5d..7449aa7b280 100644 --- a/packages/wdio-allure-reporter/package.json +++ b/packages/wdio-allure-reporter/package.json @@ -9,7 +9,7 @@ "engines": { "node": ">=12.0.0" }, - "types": "./allure-reporter.d.ts", + "types": "./build/index.d.ts", "repository": { "type": "git", "url": "git://github.com/webdriverio/webdriverio.git" @@ -25,6 +25,7 @@ }, "dependencies": { "@wdio/reporter": "6.11.0", + "@wdio/types": "^6.10.6", "allure-js-commons": "^1.3.2", "strip-ansi": "^6.0.0" }, diff --git a/packages/wdio-allure-reporter/src/@types/allure-js-commons.ts b/packages/wdio-allure-reporter/src/@types/allure-js-commons.ts deleted file mode 100644 index 0f8c90ec209..00000000000 --- a/packages/wdio-allure-reporter/src/@types/allure-js-commons.ts +++ /dev/null @@ -1,100 +0,0 @@ -// Allure is providing its own types in v2, but there's too many changes, -// so this is temporarily copied here to customize typings with allure 1 -// Type definitions for allure-js-commons 0.0 -// Project: https://github.com/allure-framework/allure-js -// Definitions by: Denis Artyuhovich -// Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped -// TypeScript Version: 2.7 -declare module 'allure-js-commons/beans/step' { - class Step {} - export = Step -} - -declare module 'allure-js-commons' { - class Allure { - constructor(); - - setOptions(options: Allure.Options): void; - getCurrentSuite(): Allure.Suite; - getCurrentTest(): Allure.Test; - startSuite(suiteName: string, timestamp?: number): void; - endSuite(timestamp?: number): void; - startCase(testName: string, timestamp?: number): void; - endCase(status: Allure.Status, err?: {}, timestamp?: number): void; - startStep(stepName?: string, timestamp?: number): void; - endStep(status: Allure.Status, timestamp?: number): void; - setDescription(description: string, timestamp?: number): void; - addAttachment(attachmentName: string, buffer: any, type?: string): void; - pendingCase(testName: string, timestamp?: number): void; - } - - export = Allure; - namespace Allure { - interface Options { - targetDir: string; - } - - // type Status = "passed" | "pending" | "skipped" | "failed" | "broken"; - type Status = 'passed' | 'pending' | 'skipped' | 'failed' | 'broken' | 'canceled'; - - enum TYPE { - TEXT = 'text', - HTML = 'html', - MARKDOWN = 'markdown' - } - - class Suite { - currentStep: Allure.Step; - testcases: string[]; - name: string; - - constructor(name: string, timestamp?: number); - - end(timestamp?: number): void; - hasTests(): boolean; - addTest(test: Test): boolean; - toXML(): string; - } - - class Test { - steps: Allure.Step[]; - name: string; - - constructor(name: string, timestamp?: number); - - setDescription(description: string | undefined, type?: TYPE): void; - addLabel(name: string, value?: string): void; - addParameter(kind: any, name: string, value?: string): void; - addStep(step: Step): void; - addAttachment(attachment: Attachment): void; - end(status: Status, error: Error, timestamp?: number): void; - toXML(): string; - } - - class Description { - constructor(value: string, type: TYPE); - - toXML(): string; - } - - class Step { - attachments: Attachment[]; - - constructor(name: string, timestamp?: number); - - addStep(step: Step): void; - addAttachment(attachment: Attachment): void; - end(status: Status, error: Error, timestamp?: number): void; - toXML(): string; - } - - class Attachment { - constructor(title: string, source: any, size: number, mime: string); - - addStep(step: Step): void; - addAttachment(attachment: Attachment): void; - end(status: Status, error: Error, timestamp?: number): void; - toXML(): string; - } - } -} diff --git a/packages/wdio-allure-reporter/src/constants.ts b/packages/wdio-allure-reporter/src/constants.ts index 4bf5ea8bd47..47ca5280c92 100644 --- a/packages/wdio-allure-reporter/src/constants.ts +++ b/packages/wdio-allure-reporter/src/constants.ts @@ -1,44 +1,44 @@ -import Allure from 'allure-js-commons' - -export const PASSED = 'passed' -export const FAILED = 'failed' -export const BROKEN = 'broken' -export const PENDING = 'pending' -export const CANCELED = 'canceled' -export const SKIPPED = 'skipped' - -const testStatuses: Record = { - PASSED, - FAILED, - BROKEN, - PENDING -} as const -const stepStatuses: Record = { - PASSED, - FAILED, - BROKEN, - CANCELED, - SKIPPED -} as const - -const events = { - addLabel: 'allure:addLabel', - addFeature: 'allure:addFeature', - addStory: 'allure:addStory', - addSeverity: 'allure:addSeverity', - addIssue: 'allure:addIssue', - addTestId: 'allure:addTestId', - addEnvironment: 'allure:addEnvironment', - addDescription: 'allure:addDescription', - addAttachment: 'allure:addAttachment', - startStep: 'allure:startStep', - endStep: 'allure:endStep', - addStep: 'allure:addStep', - addArgument: 'allure:addArgument' -} - -const mochaEachHooks = ['"before each" hook', '"after each" hook'] -const mochaAllHooks = ['"before all" hook', '"after all" hook'] -const linkPlaceholder = '{}' - -export { testStatuses, stepStatuses, events, mochaEachHooks, mochaAllHooks, linkPlaceholder } +import type { Status } from './types' + +export const PASSED = 'passed' +export const FAILED = 'failed' +export const BROKEN = 'broken' +export const PENDING = 'pending' +export const CANCELED = 'canceled' +export const SKIPPED = 'skipped' + +const testStatuses: Record = { + PASSED, + FAILED, + BROKEN, + PENDING +} as const +const stepStatuses: Record = { + PASSED, + FAILED, + BROKEN, + CANCELED, + SKIPPED +} as const + +const events = { + addLabel: 'allure:addLabel', + addFeature: 'allure:addFeature', + addStory: 'allure:addStory', + addSeverity: 'allure:addSeverity', + addIssue: 'allure:addIssue', + addTestId: 'allure:addTestId', + addEnvironment: 'allure:addEnvironment', + addDescription: 'allure:addDescription', + addAttachment: 'allure:addAttachment', + startStep: 'allure:startStep', + endStep: 'allure:endStep', + addStep: 'allure:addStep', + addArgument: 'allure:addArgument' +} as const + +const mochaEachHooks = ['"before each" hook', '"after each" hook'] as const +const mochaAllHooks = ['"before all" hook', '"after all" hook'] as const +const linkPlaceholder = '{}' + +export { testStatuses, stepStatuses, events, mochaEachHooks, mochaAllHooks, linkPlaceholder } diff --git a/packages/wdio-allure-reporter/src/index.ts b/packages/wdio-allure-reporter/src/index.ts index b0eb067f0fe..2ef8ff2be6d 100644 --- a/packages/wdio-allure-reporter/src/index.ts +++ b/packages/wdio-allure-reporter/src/index.ts @@ -1,648 +1,672 @@ -import WDIOReporter, { SuiteStats, Tag, HookStats, RunnerStats, TestStats, BeforeCommandArgs, AfterCommandArgs, CommandArgs } from '@wdio/reporter' -import Allure from 'allure-js-commons' -import Step from 'allure-js-commons/beans/step' -import { getTestStatus, isEmpty, tellReporter, isMochaEachHooks, getErrorFromFailedTest, isMochaAllHooks, getLinkByTemplate } from './utils' -import { events, PASSED, PENDING, SKIPPED, stepStatuses } from './constants' -import { AddAttachmentEventArgs, AddDescriptionEventArgs, AddEnvironmentEventArgs, AddFeatureEventArgs, AddIssueEventArgs, AddLabelEventArgs, AddSeverityEventArgs, AddStoryEventArgs, AddTestIdEventArgs, AllureReporterOptions } from './types' - -class AllureReporter extends WDIOReporter { - private _allure: Allure; - private _capabilities: WebDriver.DesiredCapabilities; - private _isMultiremote?: boolean; - private _config: WebdriverIO.Config ; - private _lastScreenshot?: string; - private _options: AllureReporterOptions; - - constructor(options: AllureReporterOptions = {}) { - const outputDir = options.outputDir || 'allure-results' - super({ - ...options, - outputDir, - }) - this._allure = new Allure() - this._capabilities = {} - this._config = {} - this._options = options - - this._allure.setOptions({ targetDir: outputDir }) - this.registerListeners() - - this._lastScreenshot = undefined - } - - registerListeners() { - process.on(events.addLabel, this.addLabel.bind(this)) - process.on(events.addFeature, this.addFeature.bind(this)) - process.on(events.addStory, this.addStory.bind(this)) - process.on(events.addSeverity, this.addSeverity.bind(this)) - process.on(events.addIssue, this.addIssue.bind(this)) - process.on(events.addTestId, this.addTestId.bind(this)) - process.on(events.addEnvironment, this.addEnvironment.bind(this)) - process.on(events.addAttachment, this.addAttachment.bind(this)) - process.on(events.addDescription, this.addDescription.bind(this)) - process.on(events.startStep, this.startStep.bind(this)) - process.on(events.endStep, this.endStep.bind(this)) - process.on(events.addStep, this.addStep.bind(this)) - process.on(events.addArgument, this.addArgument.bind(this)) - } - - onRunnerStart(runner: RunnerStats) { - this._config = runner.config - this._capabilities = runner.capabilities - this._isMultiremote = runner.isMultiremote || false - } - - onSuiteStart(suite: SuiteStats) { - if (this._options.useCucumberStepReporter) { - if (suite.type === 'feature') { - // handle cucumber features as allure "suite" - return this._allure.startSuite(suite.title) - } - - // handle cucumber scenarii as allure "case" instead of "suite" - this._allure.startCase(suite.title) - const currentTest = this._allure.getCurrentTest() - this.getLabels(suite).forEach(({ name, value }) => { - currentTest.addLabel(name, value) - }) - if (suite.description) { - this.addDescription(suite) - } - return this.setCaseParameters(suite.cid) - } - - const currentSuite = this._allure.getCurrentSuite() - const prefix = currentSuite ? currentSuite.name + ': ' : '' - this._allure.startSuite(prefix + suite.title) - } - - onSuiteEnd(suite: SuiteStats) { - if (this._options.useCucumberStepReporter && suite.type === 'scenario') { - // passing hooks are missing the 'state' property - suite.hooks = suite.hooks!.map((hook) => { - hook.state = hook.state ? hook.state : 'passed' - return hook - }) - const suiteChildren = [...suite.tests!, ...suite.hooks] - const isPassed = !suiteChildren.some(item => item.state !== 'passed') - if (isPassed) { - return this._allure.endCase('passed') - } - - // A scenario is it skipped if is not passed and every steps/hooks are passed or skipped - const isSkipped = suiteChildren.every(item => [PASSED, SKIPPED].indexOf(item.state!) >= 0) - if (isSkipped) { - return this._allure.endCase(PENDING) - } - - // Only close passing and skipped tests because - // failing tests are closed in onTestFailed event - return - } - - this._allure.endSuite() - } - - onTestStart(test: TestStats | HookStats) { - const testTitle = test.currentTest ? test.currentTest : test.title - if (this.isAnyTestRunning() && this._allure.getCurrentTest().name == testTitle) { - // Test already in progress, most likely started by a before each hook - this.setCaseParameters(test.cid) - return - } - - if (this._options.useCucumberStepReporter) { - return this._allure.startStep(testTitle) - } - - this._allure.startCase(testTitle) - this.setCaseParameters(test.cid) - } - - setCaseParameters(cid: string | undefined) { - const currentTest = this._allure.getCurrentTest() - - if (!this._isMultiremote) { - const { browserName, deviceName, desired, device } = this._capabilities - let targetName = device || browserName || deviceName || cid - // custom mobile grids can have device information in a `desired` cap - if (desired && desired.deviceName && desired.platformVersion) { - targetName = `${device || desired.deviceName} ${desired.platformVersion}` - } - const browserstackVersion = this._capabilities.os_version || this._capabilities.osVersion - const version = browserstackVersion || this._capabilities.browserVersion || this._capabilities.version || this._capabilities.platformVersion || '' - const paramName = (deviceName || device) ? 'device' : 'browser' - const paramValue = version ? `${targetName}-${version}` : targetName - currentTest.addParameter('argument', paramName, paramValue) - } else { - currentTest.addParameter('argument', 'isMultiremote', 'true') - } - - // Allure analytics labels. See https://github.com/allure-framework/allure2/blob/master/Analytics.md - currentTest.addLabel('language', 'javascript') - currentTest.addLabel('framework', 'wdio') - currentTest.addLabel('thread', cid) - } - - getLabels({ - tags - }: SuiteStats) { - const labels: { name: string, value: string }[] = [] - if (tags) { - (tags as Tag[]).forEach((tag: Tag) => { - const label = tag.name.replace(/[@]/, '').split('=') - if (label.length === 2) { - labels.push({ name: label[0], value: label[1] }) - } - }) - } - return labels - } - - onTestPass() { - if (this._options.useCucumberStepReporter) { - return this._allure.endStep('passed') - } - - this._allure.endCase(PASSED) - } - - onTestFail(test: TestStats | HookStats) { - if (this._options.useCucumberStepReporter) { - const testStatus = getTestStatus(test, this._config) - const stepStatus: Allure.Status = Object.values(stepStatuses).indexOf(testStatus) >= 0 ? - testStatus : 'failed' - this._allure.endStep(stepStatus) - this._allure.endCase(testStatus, getErrorFromFailedTest(test)) - return - } - - if (!this.isAnyTestRunning()) { // is any CASE running - - this.onTestStart(test) - } else { - - this._allure.getCurrentTest().name = test.title - } - - const status = getTestStatus(test, this._config) - while (this._allure.getCurrentSuite().currentStep instanceof Step) { - this._allure.endStep(status) - } - - this._allure.endCase(status, getErrorFromFailedTest(test)) - } - - onTestSkip(test: TestStats) { - if (this._options.useCucumberStepReporter) { - this._allure.endStep('canceled') - } else if (!this._allure.getCurrentTest() || this._allure.getCurrentTest().name !== test.title) { - this._allure.pendingCase(test.title) - } else { - this._allure.endCase('pending') - } - } - - onBeforeCommand(command: BeforeCommandArgs) { - if (!this.isAnyTestRunning()) { - return - } - - const { disableWebdriverStepsReporting } = this._options - - if (disableWebdriverStepsReporting || this._isMultiremote) { - return - } - - this._allure.startStep(command.method - ? `${command.method} ${command.endpoint}` - : command.command - ) - - const payload = command.body || command.params - if (!isEmpty(payload)) { - this.dumpJSON('Request', payload) - } - } - - onAfterCommand(command: AfterCommandArgs) { - const { disableWebdriverStepsReporting, disableWebdriverScreenshotsReporting } = this._options - if (this.isScreenshotCommand(command) && command.result.value) { - if (!disableWebdriverScreenshotsReporting) { - this._lastScreenshot = command.result.value - } - } - - if (!this.isAnyTestRunning()) { - return - } - - this.attachScreenshot() - - if (this._isMultiremote) { - return - } - - if (!disableWebdriverStepsReporting) { - if (command.result && command.result.value && !this.isScreenshotCommand(command)) { - this.dumpJSON('Response', command.result.value) - } - - const suite = this._allure.getCurrentSuite() - if (!suite || !(suite.currentStep instanceof Step)) { - return - } - - this._allure.endStep('passed') - } - } - - onHookStart(hook: HookStats) { - // ignore global hooks - if (!hook.parent || !this._allure.getCurrentSuite()) { - return false - } - - // add beforeEach / afterEach hook as step to test - if (this._options.disableMochaHooks && isMochaEachHooks(hook.title)) { - if (this._allure.getCurrentTest()) { - this._allure.startStep(hook.title) - } - return - } - - // don't add hook as test to suite for mocha All hooks - if (this._options.disableMochaHooks && isMochaAllHooks(hook.title)) { - return - } - - // add hook as test to suite - this.onTestStart(hook) - } - - onHookEnd(hook: HookStats) { - // ignore global hooks - if (!hook.parent || !this._allure.getCurrentSuite() || (this._options.disableMochaHooks && !isMochaAllHooks(hook.title) && !this._allure.getCurrentTest())) { - return false - } - - // set beforeEach / afterEach hook (step) status - if (this._options.disableMochaHooks && isMochaEachHooks(hook.title)) { - if (hook.error) { - this._allure.endStep('failed') - } else { - this._allure.endStep('passed') - } - return - } - - // set hook (test) status - if (hook.error) { - if (this._options.disableMochaHooks && isMochaAllHooks(hook.title)) { - this.onTestStart(hook) - this.attachScreenshot() - } - this.onTestFail(hook) - } else if (this._options.disableMochaHooks || this._options.useCucumberStepReporter) { - if (!isMochaAllHooks(hook.title)) { - this.onTestPass() - - // remove hook from suite if it has no steps - if (this._allure.getCurrentTest().steps.length === 0 && !this._options.useCucumberStepReporter) { - this._allure.getCurrentSuite().testcases.pop() - } else if (this._options.useCucumberStepReporter) { - // remove hook when it's registered as a step and if it's passed - const step = this._allure.getCurrentTest().steps.pop() - - // if it had any attachments, reattach them to current test - if (step && step.attachments.length >= 1) { - step.attachments.forEach(attachment => { - this._allure.getCurrentTest().addAttachment(attachment) - }) - } - } - } - } else if (!this._options.disableMochaHooks) this.onTestPass() - } - - addLabel({ - name, - value - }: AddLabelEventArgs) { - if (!this.isAnyTestRunning()) { - return false - } - - const test = this._allure.getCurrentTest() - test.addLabel(name, value) - } - - addStory({ - storyName - }: AddStoryEventArgs) { - if (!this.isAnyTestRunning()) { - return false - } - - const test = this._allure.getCurrentTest() - test.addLabel('story', storyName) - } - - addFeature({ - featureName - }: AddFeatureEventArgs) { - if (!this.isAnyTestRunning()) { - return false - } - - const test = this._allure.getCurrentTest() - test.addLabel('feature', featureName) - } - - addSeverity({ - severity - }: AddSeverityEventArgs) { - if (!this.isAnyTestRunning()) { - return false - } - - const test = this._allure.getCurrentTest() - test.addLabel('severity', severity) - } - - addIssue({ - issue - }: AddIssueEventArgs) { - if (!this.isAnyTestRunning()) { - return false - } - - const test = this._allure.getCurrentTest() - const issueLink = getLinkByTemplate(this._options.issueLinkTemplate, issue) - test.addLabel('issue', issueLink) - } - - addTestId({ - testId - }: AddTestIdEventArgs) { - if (!this.isAnyTestRunning()) { - return false - } - - const test = this._allure.getCurrentTest() - const tmsLink = getLinkByTemplate(this._options.tmsLinkTemplate, testId) - test.addLabel('testId', tmsLink) - } - - addEnvironment({ - name, - value - }: AddEnvironmentEventArgs) { - if (!this.isAnyTestRunning()) { - return false - } - - const test = this._allure.getCurrentTest() - test.addParameter('environment-variable', name, value) - } - - addDescription({ - description, - descriptionType - }: AddDescriptionEventArgs) { - if (!this.isAnyTestRunning()) { - return false - } - - const test = this._allure.getCurrentTest() - test.setDescription(description, descriptionType) - } - - addAttachment({ - name, - content, - type = 'text/plain' - }: AddAttachmentEventArgs) { - if (!this.isAnyTestRunning()) { - return false - } - - if (type === 'application/json') { - this.dumpJSON(name, content as object) - } else { - this._allure.addAttachment(name, Buffer.from(content as string), type) - } - } - - startStep(title: string) { - if (!this.isAnyTestRunning()) { - return false - } - this._allure.startStep(title) - } - - endStep(status: Allure.Status) { - if (!this.isAnyTestRunning()) { - return false - } - this._allure.endStep(status) - } - - addStep({ - step - }: any) { - if (!this.isAnyTestRunning()) { - return false - } - this.startStep(step.title) - if (step.attachment) { - this.addAttachment(step.attachment) - } - this.endStep(step.status) - } - - addArgument({ - name, - value - }: any) { - if (!this.isAnyTestRunning()) { - return false - } - - const test = this._allure.getCurrentTest() - test.addParameter('argument', name, value) - } - - isAnyTestRunning() { - return this._allure.getCurrentSuite() && this._allure.getCurrentTest() - } - - isScreenshotCommand(command: CommandArgs) { - const isScrenshotEndpoint = /\/session\/[^/]*(\/element\/[^/]*)?\/screenshot/ - return ( - // WebDriver protocol - (command.endpoint && isScrenshotEndpoint.test(command.endpoint)) || - // DevTools protocol - command.command === 'takeScreenshot' - ) - } - - dumpJSON(name: string, json: object) { - const content = JSON.stringify(json, null, 2) - const isStr = typeof content === 'string' - this._allure.addAttachment(name, isStr ? content : `${content}`, isStr ? 'application/json' : 'text/plain') - } - - attachScreenshot() { - if (this._lastScreenshot && !this._options.disableWebdriverScreenshotsReporting) { - this._allure.addAttachment('Screenshot', Buffer.from(this._lastScreenshot, 'base64')) - this._lastScreenshot = undefined - } - } - - /** - * Assign feature to test - * @name addFeature - * @param {(string)} featureName - feature name or an array of names - */ - static addFeature = (featureName: string) => { - tellReporter(events.addFeature, { featureName }) - } - - /** - * Assign label to test - * @name addLabel - * @param {string} name - label name - * @param {string} value - label value - */ - static addLabel = (name: string, value: string) => { - tellReporter(events.addLabel, { name, value }) - } - /** - * Assign severity to test - * @name addSeverity - * @param {string} severity - severity value - */ - static addSeverity = (severity: string) => { - tellReporter(events.addSeverity, { severity }) - } - - /** - * Assign issue id to test - * @name addIssue - * @param {string} issue - issue id value - */ - static addIssue = (issue: string) => { - tellReporter(events.addIssue, { issue }) - } - - /** - * Assign TMS test id to test - * @name addTestId - * @param {string} testId - test id value - */ - static addTestId = (testId: string) => { - tellReporter(events.addTestId, { testId }) - } - - /** - * Assign story to test - * @name addStory - * @param {string} storyName - story name for test - */ - static addStory = (storyName: string) => { - tellReporter(events.addStory, { storyName }) - } - - /** - * Add environment value - * @name addEnvironment - * @param {string} name - environment name - * @param {string} value - environment value - */ - static addEnvironment = (name: string, value: string) => { - tellReporter(events.addEnvironment, { name, value }) - } - - /** - * Assign test description to test - * @name addDescription - * @param {string} description - description for test - * @param {string} descriptionType - description type 'text'\'html'\'markdown' - */ - static addDescription = (description: string, descriptionType: string) => { - tellReporter(events.addDescription, { description, descriptionType }) - } - - /** - * Add attachment - * @name addAttachment - * @param {string} name - attachment file name - * @param {*} content - attachment content - * @param {string=} mimeType - attachment mime type - */ - static addAttachment = (name: string, content: string | Buffer | object, type: string) => { - if (!type) { - type = content instanceof Buffer ? 'image/png' : typeof content === 'string' ? 'text/plain' : 'application/json' - } - tellReporter(events.addAttachment, { name, content, type }) - } - - /** - * Start allure step - * @name startStep - * @param {string} title - step name in report - */ - static startStep = (title: string) => { - tellReporter(events.startStep, title) - } - - /** - * End current allure step - * @name endStep - * @param {StepStatus} [status='passed'] - step status - */ - static endStep = (status: Allure.Status = 'passed') => { - if (!Object.values(stepStatuses).includes(status)) { - throw new Error(`Step status must be ${Object.values(stepStatuses).join(' or ')}. You tried to set "${status}"`) - } - tellReporter(events.endStep, status) - } - - /** - * Create allure step - * @name addStep - * @param {string} title - step name in report - * @param {Object} [attachmentObject={}] - attachment for step - * @param {string} attachmentObject.content - attachment content - * @param {string} [attachmentObject.name='attachment'] - attachment name - * @param {string} [attachmentObject.type='text/plain'] - attachment type - * @param {string} [status='passed'] - step status - */ - static addStep = (title: string, { - content, - name = 'attachment', - type = 'text/plain' - }: any = {}, status: Allure.Status = 'passed') => { - if (!Object.values(stepStatuses).includes(status)) { - throw new Error(`Step status must be ${Object.values(stepStatuses).join(' or ')}. You tried to set "${status}"`) - } - - const step = content ? { title, attachment: { content, name, type }, status } : { title, status } - tellReporter(events.addStep, { step }) - } - - /** - * Add additional argument to test - * @name addArgument - * @param {string} name - argument name - * @param {string} value - argument value - */ - static addArgument = (name: string, value: string) => { - tellReporter(events.addArgument, { name, value }) - } -} - -export default AllureReporter - -export { AllureReporterOptions } \ No newline at end of file +import WDIOReporter, { + SuiteStats, Tag, HookStats, RunnerStats, TestStats, BeforeCommandArgs, + AfterCommandArgs, CommandArgs +} from '@wdio/reporter' +import { Capabilities, Options } from '@wdio/types' + +import { + getTestStatus, isEmpty, tellReporter, isMochaEachHooks, getErrorFromFailedTest, + isMochaAllHooks, getLinkByTemplate +} from './utils' +import { events, PASSED, PENDING, SKIPPED, stepStatuses } from './constants' +import { + AddAttachmentEventArgs, AddDescriptionEventArgs, AddEnvironmentEventArgs, + AddFeatureEventArgs, AddIssueEventArgs, AddLabelEventArgs, AddSeverityEventArgs, + AddStoryEventArgs, AddTestIdEventArgs, AllureReporterOptions, Status +} from './types' + +/** + * Allure v1 has no proper TS support + * ToDo(Christian): update to Allure v2 (https://github.com/webdriverio/webdriverio/issues/6313) + */ +const Allure = require('allure-js-commons') +const Step = require('allure-js-commons/beans/step') + +class AllureReporter extends WDIOReporter { + private _allure: any + private _capabilities: Capabilities.RemoteCapability + private _isMultiremote?: boolean + private _config?: Options.Testrunner + private _lastScreenshot?: string + private _options: AllureReporterOptions + + constructor(options: AllureReporterOptions = {}) { + const outputDir = options.outputDir || 'allure-results' + super({ + ...options, + outputDir, + }) + this._allure = new Allure() + this._capabilities = {} + this._options = options + + this._allure.setOptions({ targetDir: outputDir }) + this.registerListeners() + + this._lastScreenshot = undefined + } + + registerListeners() { + process.on(events.addLabel, this.addLabel.bind(this)) + process.on(events.addFeature, this.addFeature.bind(this)) + process.on(events.addStory, this.addStory.bind(this)) + process.on(events.addSeverity, this.addSeverity.bind(this)) + process.on(events.addIssue, this.addIssue.bind(this)) + process.on(events.addTestId, this.addTestId.bind(this)) + process.on(events.addEnvironment, this.addEnvironment.bind(this)) + process.on(events.addAttachment, this.addAttachment.bind(this)) + process.on(events.addDescription, this.addDescription.bind(this)) + process.on(events.startStep, this.startStep.bind(this)) + process.on(events.endStep, this.endStep.bind(this)) + process.on(events.addStep, this.addStep.bind(this)) + process.on(events.addArgument, this.addArgument.bind(this)) + } + + onRunnerStart(runner: RunnerStats) { + this._config = runner.config + this._capabilities = runner.capabilities + this._isMultiremote = runner.isMultiremote || false + } + + onSuiteStart(suite: SuiteStats) { + if (this._options.useCucumberStepReporter) { + if (suite.type === 'feature') { + // handle cucumber features as allure "suite" + return this._allure.startSuite(suite.title) + } + + // handle cucumber scenarii as allure "case" instead of "suite" + this._allure.startCase(suite.title) + const currentTest = this._allure.getCurrentTest() + this.getLabels(suite).forEach(({ name, value }) => { + currentTest.addLabel(name, value) + }) + if (suite.description) { + this.addDescription(suite) + } + return this.setCaseParameters(suite.cid) + } + + const currentSuite = this._allure.getCurrentSuite() + const prefix = currentSuite ? currentSuite.name + ': ' : '' + this._allure.startSuite(prefix + suite.title) + } + + onSuiteEnd(suite: SuiteStats) { + if (this._options.useCucumberStepReporter && suite.type === 'scenario') { + // passing hooks are missing the 'state' property + suite.hooks = suite.hooks!.map((hook) => { + hook.state = hook.state ? hook.state : 'passed' + return hook + }) + const suiteChildren = [...suite.tests!, ...suite.hooks] + const isPassed = !suiteChildren.some(item => item.state !== 'passed') + if (isPassed) { + return this._allure.endCase('passed') + } + + // A scenario is it skipped if is not passed and every steps/hooks are passed or skipped + const isSkipped = suiteChildren.every(item => [PASSED, SKIPPED].indexOf(item.state!) >= 0) + if (isSkipped) { + return this._allure.endCase(PENDING) + } + + // Only close passing and skipped tests because + // failing tests are closed in onTestFailed event + return + } + + this._allure.endSuite() + } + + onTestStart(test: TestStats | HookStats) { + const testTitle = test.currentTest ? test.currentTest : test.title + if (this.isAnyTestRunning() && this._allure.getCurrentTest().name == testTitle) { + // Test already in progress, most likely started by a before each hook + this.setCaseParameters(test.cid) + return + } + + if (this._options.useCucumberStepReporter) { + return this._allure.startStep(testTitle) + } + + this._allure.startCase(testTitle) + this.setCaseParameters(test.cid) + } + + setCaseParameters(cid: string | undefined) { + const currentTest = this._allure.getCurrentTest() + + if (!this._isMultiremote) { + const caps = this._capabilities as Capabilities.DesiredCapabilities + const { browserName, deviceName, desired, device } = caps + let targetName = device || browserName || deviceName || cid + // custom mobile grids can have device information in a `desired` cap + if (desired && desired.deviceName && desired.platformVersion) { + targetName = `${device || desired.deviceName} ${desired.platformVersion}` + } + const browserstackVersion = caps.os_version || caps.osVersion + const version = browserstackVersion || caps.browserVersion || caps.version || caps.platformVersion || '' + const paramName = (deviceName || device) ? 'device' : 'browser' + const paramValue = version ? `${targetName}-${version}` : targetName + currentTest.addParameter('argument', paramName, paramValue) + } else { + currentTest.addParameter('argument', 'isMultiremote', 'true') + } + + // Allure analytics labels. See https://github.com/allure-framework/allure2/blob/master/Analytics.md + currentTest.addLabel('language', 'javascript') + currentTest.addLabel('framework', 'wdio') + currentTest.addLabel('thread', cid) + } + + getLabels({ + tags + }: SuiteStats) { + const labels: { name: string, value: string }[] = [] + if (tags) { + (tags as Tag[]).forEach((tag: Tag) => { + const label = tag.name.replace(/[@]/, '').split('=') + if (label.length === 2) { + labels.push({ name: label[0], value: label[1] }) + } + }) + } + return labels + } + + onTestPass() { + if (this._options.useCucumberStepReporter) { + return this._allure.endStep('passed') + } + + this._allure.endCase(PASSED) + } + + onTestFail(test: TestStats | HookStats) { + if (this._options.useCucumberStepReporter) { + const testStatus = getTestStatus(test, this._config) + const stepStatus: Status = Object.values(stepStatuses).indexOf(testStatus) >= 0 ? + testStatus : 'failed' + this._allure.endStep(stepStatus) + this._allure.endCase(testStatus, getErrorFromFailedTest(test)) + return + } + + if (!this.isAnyTestRunning()) { // is any CASE running + + this.onTestStart(test) + } else { + + this._allure.getCurrentTest().name = test.title + } + + const status = getTestStatus(test, this._config) + while (this._allure.getCurrentSuite().currentStep instanceof Step) { + this._allure.endStep(status) + } + + this._allure.endCase(status, getErrorFromFailedTest(test)) + } + + onTestSkip(test: TestStats) { + if (this._options.useCucumberStepReporter) { + this._allure.endStep('canceled') + } else if (!this._allure.getCurrentTest() || this._allure.getCurrentTest().name !== test.title) { + this._allure.pendingCase(test.title) + } else { + this._allure.endCase('pending') + } + } + + onBeforeCommand(command: BeforeCommandArgs) { + if (!this.isAnyTestRunning()) { + return + } + + const { disableWebdriverStepsReporting } = this._options + + if (disableWebdriverStepsReporting || this._isMultiremote) { + return + } + + this._allure.startStep(command.method + ? `${command.method} ${command.endpoint}` + : command.command + ) + + const payload = command.body || command.params + if (!isEmpty(payload)) { + this.dumpJSON('Request', payload) + } + } + + onAfterCommand(command: AfterCommandArgs) { + const { disableWebdriverStepsReporting, disableWebdriverScreenshotsReporting } = this._options + if (this.isScreenshotCommand(command) && command.result.value) { + if (!disableWebdriverScreenshotsReporting) { + this._lastScreenshot = command.result.value + } + } + + if (!this.isAnyTestRunning()) { + return + } + + this.attachScreenshot() + + if (this._isMultiremote) { + return + } + + if (!disableWebdriverStepsReporting) { + if (command.result && command.result.value && !this.isScreenshotCommand(command)) { + this.dumpJSON('Response', command.result.value) + } + + const suite = this._allure.getCurrentSuite() + if (!suite || !(suite.currentStep instanceof Step)) { + return + } + + this._allure.endStep('passed') + } + } + + onHookStart(hook: HookStats) { + // ignore global hooks + if (!hook.parent || !this._allure.getCurrentSuite()) { + return false + } + + // add beforeEach / afterEach hook as step to test + if (this._options.disableMochaHooks && isMochaEachHooks(hook.title)) { + if (this._allure.getCurrentTest()) { + this._allure.startStep(hook.title) + } + return + } + + // don't add hook as test to suite for mocha All hooks + if (this._options.disableMochaHooks && isMochaAllHooks(hook.title)) { + return + } + + // add hook as test to suite + this.onTestStart(hook) + } + + onHookEnd(hook: HookStats) { + // ignore global hooks + if (!hook.parent || !this._allure.getCurrentSuite() || (this._options.disableMochaHooks && !isMochaAllHooks(hook.title) && !this._allure.getCurrentTest())) { + return false + } + + // set beforeEach / afterEach hook (step) status + if (this._options.disableMochaHooks && isMochaEachHooks(hook.title)) { + if (hook.error) { + this._allure.endStep('failed') + } else { + this._allure.endStep('passed') + } + return + } + + // set hook (test) status + if (hook.error) { + if (this._options.disableMochaHooks && isMochaAllHooks(hook.title)) { + this.onTestStart(hook) + this.attachScreenshot() + } + this.onTestFail(hook) + } else if (this._options.disableMochaHooks || this._options.useCucumberStepReporter) { + if (!isMochaAllHooks(hook.title)) { + this.onTestPass() + + // remove hook from suite if it has no steps + if (this._allure.getCurrentTest().steps.length === 0 && !this._options.useCucumberStepReporter) { + this._allure.getCurrentSuite().testcases.pop() + } else if (this._options.useCucumberStepReporter) { + // remove hook when it's registered as a step and if it's passed + const step = this._allure.getCurrentTest().steps.pop() + + // if it had any attachments, reattach them to current test + if (step && step.attachments.length >= 1) { + step.attachments.forEach((attachment: any) => { + this._allure.getCurrentTest().addAttachment(attachment) + }) + } + } + } + } else if (!this._options.disableMochaHooks) this.onTestPass() + } + + addLabel({ + name, + value + }: AddLabelEventArgs) { + if (!this.isAnyTestRunning()) { + return false + } + + const test = this._allure.getCurrentTest() + test.addLabel(name, value) + } + + addStory({ + storyName + }: AddStoryEventArgs) { + if (!this.isAnyTestRunning()) { + return false + } + + const test = this._allure.getCurrentTest() + test.addLabel('story', storyName) + } + + addFeature({ + featureName + }: AddFeatureEventArgs) { + if (!this.isAnyTestRunning()) { + return false + } + + const test = this._allure.getCurrentTest() + test.addLabel('feature', featureName) + } + + addSeverity({ + severity + }: AddSeverityEventArgs) { + if (!this.isAnyTestRunning()) { + return false + } + + const test = this._allure.getCurrentTest() + test.addLabel('severity', severity) + } + + addIssue({ + issue + }: AddIssueEventArgs) { + if (!this.isAnyTestRunning()) { + return false + } + + const test = this._allure.getCurrentTest() + const issueLink = getLinkByTemplate(this._options.issueLinkTemplate, issue) + test.addLabel('issue', issueLink) + } + + addTestId({ + testId + }: AddTestIdEventArgs) { + if (!this.isAnyTestRunning()) { + return false + } + + const test = this._allure.getCurrentTest() + const tmsLink = getLinkByTemplate(this._options.tmsLinkTemplate, testId) + test.addLabel('testId', tmsLink) + } + + addEnvironment({ + name, + value + }: AddEnvironmentEventArgs) { + if (!this.isAnyTestRunning()) { + return false + } + + const test = this._allure.getCurrentTest() + test.addParameter('environment-variable', name, value) + } + + addDescription({ + description, + descriptionType + }: AddDescriptionEventArgs) { + if (!this.isAnyTestRunning()) { + return false + } + + const test = this._allure.getCurrentTest() + test.setDescription(description, descriptionType) + } + + addAttachment({ + name, + content, + type = 'text/plain' + }: AddAttachmentEventArgs) { + if (!this.isAnyTestRunning()) { + return false + } + + if (type === 'application/json') { + this.dumpJSON(name, content as object) + } else { + this._allure.addAttachment(name, Buffer.from(content as string), type) + } + } + + startStep(title: string) { + if (!this.isAnyTestRunning()) { + return false + } + this._allure.startStep(title) + } + + endStep(status: Status) { + if (!this.isAnyTestRunning()) { + return false + } + this._allure.endStep(status) + } + + addStep({ + step + }: any) { + if (!this.isAnyTestRunning()) { + return false + } + this.startStep(step.title) + if (step.attachment) { + this.addAttachment(step.attachment) + } + this.endStep(step.status) + } + + addArgument({ + name, + value + }: any) { + if (!this.isAnyTestRunning()) { + return false + } + + const test = this._allure.getCurrentTest() + test.addParameter('argument', name, value) + } + + isAnyTestRunning() { + return this._allure.getCurrentSuite() && this._allure.getCurrentTest() + } + + isScreenshotCommand(command: CommandArgs) { + const isScrenshotEndpoint = /\/session\/[^/]*(\/element\/[^/]*)?\/screenshot/ + return ( + // WebDriver protocol + (command.endpoint && isScrenshotEndpoint.test(command.endpoint)) || + // DevTools protocol + command.command === 'takeScreenshot' + ) + } + + dumpJSON(name: string, json: object) { + const content = JSON.stringify(json, null, 2) + const isStr = typeof content === 'string' + this._allure.addAttachment(name, isStr ? content : `${content}`, isStr ? 'application/json' : 'text/plain') + } + + attachScreenshot() { + if (this._lastScreenshot && !this._options.disableWebdriverScreenshotsReporting) { + this._allure.addAttachment('Screenshot', Buffer.from(this._lastScreenshot, 'base64')) + this._lastScreenshot = undefined + } + } + + /** + * Assign feature to test + * @name addFeature + * @param {(string)} featureName - feature name or an array of names + */ + static addFeature = (featureName: string) => { + tellReporter(events.addFeature, { featureName }) + } + + /** + * Assign label to test + * @name addLabel + * @param {string} name - label name + * @param {string} value - label value + */ + static addLabel = (name: string, value: string) => { + tellReporter(events.addLabel, { name, value }) + } + /** + * Assign severity to test + * @name addSeverity + * @param {string} severity - severity value + */ + static addSeverity = (severity: string) => { + tellReporter(events.addSeverity, { severity }) + } + + /** + * Assign issue id to test + * @name addIssue + * @param {string} issue - issue id value + */ + static addIssue = (issue: string) => { + tellReporter(events.addIssue, { issue }) + } + + /** + * Assign TMS test id to test + * @name addTestId + * @param {string} testId - test id value + */ + static addTestId = (testId: string) => { + tellReporter(events.addTestId, { testId }) + } + + /** + * Assign story to test + * @name addStory + * @param {string} storyName - story name for test + */ + static addStory = (storyName: string) => { + tellReporter(events.addStory, { storyName }) + } + + /** + * Add environment value + * @name addEnvironment + * @param {string} name - environment name + * @param {string} value - environment value + */ + static addEnvironment = (name: string, value: string) => { + tellReporter(events.addEnvironment, { name, value }) + } + + /** + * Assign test description to test + * @name addDescription + * @param {string} description - description for test + * @param {string} descriptionType - description type 'text'\'html'\'markdown' + */ + static addDescription = (description: string, descriptionType: string) => { + tellReporter(events.addDescription, { description, descriptionType }) + } + + /** + * Add attachment + * @name addAttachment + * @param {string} name - attachment file name + * @param {*} content - attachment content + * @param {string=} mimeType - attachment mime type + */ + static addAttachment = (name: string, content: string | Buffer | object, type: string) => { + if (!type) { + type = content instanceof Buffer ? 'image/png' : typeof content === 'string' ? 'text/plain' : 'application/json' + } + tellReporter(events.addAttachment, { name, content, type }) + } + + /** + * Start allure step + * @name startStep + * @param {string} title - step name in report + */ + static startStep = (title: string) => { + tellReporter(events.startStep, title) + } + + /** + * End current allure step + * @name endStep + * @param {StepStatus} [status='passed'] - step status + */ + static endStep = (status: Status = 'passed') => { + if (!Object.values(stepStatuses).includes(status)) { + throw new Error(`Step status must be ${Object.values(stepStatuses).join(' or ')}. You tried to set "${status}"`) + } + tellReporter(events.endStep, status) + } + + /** + * Create allure step + * @name addStep + * @param {string} title - step name in report + * @param {Object} [attachmentObject={}] - attachment for step + * @param {string} attachmentObject.content - attachment content + * @param {string} [attachmentObject.name='attachment'] - attachment name + * @param {string} [attachmentObject.type='text/plain'] - attachment type + * @param {string} [status='passed'] - step status + */ + static addStep = (title: string, { + content, + name = 'attachment', + type = 'text/plain' + }: any = {}, status: Status = 'passed') => { + if (!Object.values(stepStatuses).includes(status)) { + throw new Error(`Step status must be ${Object.values(stepStatuses).join(' or ')}. You tried to set "${status}"`) + } + + const step = content ? { title, attachment: { content, name, type }, status } : { title, status } + tellReporter(events.addStep, { step }) + } + + /** + * Add additional argument to test + * @name addArgument + * @param {string} name - argument name + * @param {string} value - argument value + */ + static addArgument = (name: string, value: string) => { + tellReporter(events.addArgument, { name, value }) + } +} + +export default AllureReporter + +export { AllureReporterOptions } +export * from './types' + +declare global { + namespace WebdriverIO { + interface ReporterOption extends AllureReporterOptions {} + } +} diff --git a/packages/wdio-allure-reporter/src/types.ts b/packages/wdio-allure-reporter/src/types.ts index 7ef1266dc16..5919a0f51c4 100644 --- a/packages/wdio-allure-reporter/src/types.ts +++ b/packages/wdio-allure-reporter/src/types.ts @@ -1,85 +1,103 @@ -import { WDIOReporterBaseOptions } from '@wdio/reporter' -import Allure from 'allure-js-commons' - /** * When you add a new option, please also update the docs at ./packages/wdio-allure-reporter/README.md */ -export interface AllureReporterOptions extends WDIOReporterBaseOptions { +export interface AllureReporterOptions { /** * defaults to `./allure-results`. After a test run is complete, you will find that this directory * has been populated with an `.xml` file for each spec, plus a number of `.txt` and `.png` * files and other attachments. */ - outputDir?: string; + outputDir?: string /** * optional parameter (`false` by default), set it to true in order to change the report hierarchy * when using cucumber. Try it for yourself and see how it looks. */ - useCucumberStepReporter?: boolean; + useCucumberStepReporter?: boolean /** * optional parameter (`false` by default), set it to true in order to not fetch the `before/after` * stacktrace/screenshot/result hooks into the Allure Reporter. */ - disableMochaHooks?: boolean; + disableMochaHooks?: boolean /** * optional parameter(`false` by default), in order to log only custom steps to the reporter. */ - disableWebdriverStepsReporting?: boolean; + disableWebdriverStepsReporting?: boolean /** * optional parameter(`false` by default), in order to not attach screenshots to the reporter. */ - disableWebdriverScreenshotsReporting?: boolean; + disableWebdriverScreenshotsReporting?: boolean /** * optional parameter, in order to specify the issue link pattern. Reporter will replace `{}` placeholder * with value specified in `addIssue(value)` call parameter. * Example `https://example.org/issue/{}` */ - issueLinkTemplate?: string; + issueLinkTemplate?: string /** * optional parameter, in order to specify the tms link pattern. Reporter will replace `{}` placeholder * with value specified in `addTestId(value)` call parameter. Example `https://example.org/tms/{}` */ - tmsLinkTemplate?: string; + tmsLinkTemplate?: string } export interface AddLabelEventArgs { - name: string; - value: string; + name: string + value: string } export interface AddStoryEventArgs { - storyName: string; + storyName: string } export interface AddFeatureEventArgs { - featureName: string; + featureName: string } export interface AddSeverityEventArgs { - severity: string; + severity: string } export interface AddIssueEventArgs { - issue: string; + issue: string } export interface AddTestIdEventArgs { - testId: string; + testId: string } export interface AddEnvironmentEventArgs { - name: string; - value: string; + name: string + value: string +} + +enum TYPE { + TEXT = 'text', + HTML = 'html', + MARKDOWN = 'markdown' } export interface AddDescriptionEventArgs { - description?: string; - descriptionType?: Allure.TYPE; + description?: string + descriptionType?: TYPE } export interface AddAttachmentEventArgs { - name: string; - content: string | object; - type: string; + name: string + content: string | object + type: string } +export interface Step { + attachments: Attachment[]; + addStep(step: Step): void; + addAttachment(attachment: Attachment): void; + end(status: Status, error: Error, timestamp?: number): void; + toXML(): string; +} + +export type Status = 'passed' | 'pending' | 'skipped' | 'failed' | 'broken' | 'canceled'; +export interface Attachment { + addStep(step: Step): void; + addAttachment(attachment: Attachment): void; + end(status: Status, error: Error, timestamp?: number): void; + toXML(): string; +} diff --git a/packages/wdio-allure-reporter/src/utils.ts b/packages/wdio-allure-reporter/src/utils.ts index 58291e19c6a..a6de18eb380 100644 --- a/packages/wdio-allure-reporter/src/utils.ts +++ b/packages/wdio-allure-reporter/src/utils.ts @@ -1,110 +1,112 @@ -import process from 'process' -import CompoundError from './compoundError' -import { mochaEachHooks, mochaAllHooks, linkPlaceholder } from './constants' -import stripAnsi from 'strip-ansi' -import Allure from 'allure-js-commons' -import { HookStats, TestStats } from '@wdio/reporter' - -/** - * Get allure test status by TestStat object - * @param test {Object} - TestStat object - * @param config {Object} - wdio config object - * @private - */ -export const getTestStatus = (test: TestStats | HookStats, config: WebdriverIO.Config) : Allure.Status => { - if (config.framework === 'jasmine') { - return 'failed' - } - - if (test.error) { - if (test.error.name && test.error.message) { - const message = test.error.message.trim() - return (test.error.name === 'AssertionError' || message.includes('Expect')) ? 'failed' : 'broken' - } - - if (test.error.name) { - return test.error.name === 'AssertionError' ? 'failed' : 'broken' - } - - if (test.error.stack) { - const stackTrace = test.error.stack.trim() - return (stackTrace.startsWith('AssertionError') || stackTrace.includes('Expect')) ? 'failed' : 'broken' - } - } - - return 'broken' -} - -/** - * Check is object is empty - * @param object {Object} - * @private - */ -export const isEmpty = (object: any) => !object || Object.keys(object).length === 0 - -/** - * Is mocha beforeEach / afterEach hook - * @param title {String} - hook title - * @returns {boolean} - * @private - */ -export const isMochaEachHooks = (title: string) => mochaEachHooks.some(hook => title.includes(hook)) - -/** - * Is mocha beforeAll / afterAll hook - * @param title {String} - hook title - * @returns {boolean} - * @private - */ -export const isMochaAllHooks = (title: string) => mochaAllHooks.some(hook => title.includes(hook)) - -/** - * Call reporter - * @param {string} event - event name - * @param {Object} msg - event payload - * @private - */ -export const tellReporter = (event: string, msg: any = {}) => { - // Node 14 typings does not accept string in process.emit, but allow string in process.on - process.emit(event as any, msg) -} - -/** - * Properly format error from different test runners - * @param {Object} test - TestStat object - * @returns {Object} - error object - * @private - */ -export const getErrorFromFailedTest = (test: TestStats | HookStats) : Error | CompoundError | undefined => { - if (test.errors && Array.isArray(test.errors)) { - for (let i = 0; i < test.errors.length; i += 1){ - if (test.errors[i].message) test.errors[i].message = stripAnsi(test.errors[i].message) - if (test.errors[i].stack) test.errors[i].stack = stripAnsi(test.errors[i].stack!) - } - return test.errors.length === 1 ? test.errors[0] : new CompoundError(...test.errors as Error[]) - } - - if (test.error) { - if (test.error.message) test.error.message = stripAnsi(test.error.message) - if (test.error.stack) test.error.stack = stripAnsi(test.error.stack) - } - - return test.error -} - -/** - * Substitute task id to link template - * @param {string} template - link template - * @param {string} id - task id - * @returns {string} - link after substitution - * @private - */ -export const getLinkByTemplate = (template: string | undefined, id: string) => { - if (typeof template !== 'string') { - return id - } - if (!template.includes(linkPlaceholder)) { - throw Error(`The link template "${template}" must contain ${linkPlaceholder} substring.`) - } - return template.replace(linkPlaceholder, id) -} +import process from 'process' +import stripAnsi from 'strip-ansi' +import { HookStats, TestStats } from '@wdio/reporter' +import type { Options } from '@wdio/types' + +import CompoundError from './compoundError' +import { mochaEachHooks, mochaAllHooks, linkPlaceholder } from './constants' +import type { Status } from './types' + +/** + * Get allure test status by TestStat object + * @param test {Object} - TestStat object + * @param config {Object} - wdio config object + * @private + */ +export const getTestStatus = (test: TestStats | HookStats, config?: Options.Testrunner) : Status => { + if (config && config.framework === 'jasmine') { + return 'failed' + } + + if (test.error) { + if (test.error.name && test.error.message) { + const message = test.error.message.trim() + return (test.error.name === 'AssertionError' || message.includes('Expect')) ? 'failed' : 'broken' + } + + if (test.error.name) { + return test.error.name === 'AssertionError' ? 'failed' : 'broken' + } + + if (test.error.stack) { + const stackTrace = test.error.stack.trim() + return (stackTrace.startsWith('AssertionError') || stackTrace.includes('Expect')) ? 'failed' : 'broken' + } + } + + return 'broken' +} + +/** + * Check is object is empty + * @param object {Object} + * @private + */ +export const isEmpty = (object: any) => !object || Object.keys(object).length === 0 + +/** + * Is mocha beforeEach / afterEach hook + * @param title {String} - hook title + * @returns {boolean} + * @private + */ +export const isMochaEachHooks = (title: string) => mochaEachHooks.some(hook => title.includes(hook)) + +/** + * Is mocha beforeAll / afterAll hook + * @param title {String} - hook title + * @returns {boolean} + * @private + */ +export const isMochaAllHooks = (title: string) => mochaAllHooks.some(hook => title.includes(hook)) + +/** + * Call reporter + * @param {string} event - event name + * @param {Object} msg - event payload + * @private + */ +export const tellReporter = (event: string, msg: any = {}) => { + // Node 14 typings does not accept string in process.emit, but allow string in process.on + process.emit(event as any, msg) +} + +/** + * Properly format error from different test runners + * @param {Object} test - TestStat object + * @returns {Object} - error object + * @private + */ +export const getErrorFromFailedTest = (test: TestStats | HookStats) : Error | CompoundError | undefined => { + if (test.errors && Array.isArray(test.errors)) { + for (let i = 0; i < test.errors.length; i += 1){ + if (test.errors[i].message) test.errors[i].message = stripAnsi(test.errors[i].message) + if (test.errors[i].stack) test.errors[i].stack = stripAnsi(test.errors[i].stack!) + } + return test.errors.length === 1 ? test.errors[0] : new CompoundError(...test.errors as Error[]) + } + + if (test.error) { + if (test.error.message) test.error.message = stripAnsi(test.error.message) + if (test.error.stack) test.error.stack = stripAnsi(test.error.stack) + } + + return test.error +} + +/** + * Substitute task id to link template + * @param {string} template - link template + * @param {string} id - task id + * @returns {string} - link after substitution + * @private + */ +export const getLinkByTemplate = (template: string | undefined, id: string) => { + if (typeof template !== 'string') { + return id + } + if (!template.includes(linkPlaceholder)) { + throw Error(`The link template "${template}" must contain ${linkPlaceholder} substring.`) + } + return template.replace(linkPlaceholder, id) +} diff --git a/packages/wdio-allure-reporter/tsconfig.json b/packages/wdio-allure-reporter/tsconfig.json index e0952f8f6bb..d660189bdb9 100644 --- a/packages/wdio-allure-reporter/tsconfig.json +++ b/packages/wdio-allure-reporter/tsconfig.json @@ -3,10 +3,10 @@ "compilerOptions": { "baseUrl": ".", "outDir": "./build", - "rootDir": "./src", - "types": ["webdriverio"] + "rootDir": "./src" }, "include": [ - "src/**/*" + "src/**/*", + "../../@types" ] } diff --git a/packages/wdio-allure-reporter/tsconfig.prod.json b/packages/wdio-allure-reporter/tsconfig.prod.json index 8ff66adac36..2a0e7e84140 100644 --- a/packages/wdio-allure-reporter/tsconfig.prod.json +++ b/packages/wdio-allure-reporter/tsconfig.prod.json @@ -3,10 +3,10 @@ "compilerOptions": { "baseUrl": ".", "outDir": "./build", - "rootDir": "./src", - "types": ["webdriverio"] + "rootDir": "./src" }, "include": [ - "src/**/*" + "src/**/*", + "../../@types" ] } diff --git a/packages/wdio-appium-service/package.json b/packages/wdio-appium-service/package.json index 0d568d4809c..f78927611d9 100644 --- a/packages/wdio-appium-service/package.json +++ b/packages/wdio-appium-service/package.json @@ -29,6 +29,7 @@ "@types/fs-extra": "^9.0.4", "@wdio/config": "6.12.1", "@wdio/logger": "6.10.10", + "@wdio/types": "^6.10.6", "fs-extra": "^9.0.0", "param-case": "^3.0.0" }, @@ -38,5 +39,5 @@ "publishConfig": { "access": "public" }, - "types": "appium-service.d.ts" + "types": "./build/index.d.ts" } diff --git a/packages/wdio-appium-service/src/@types/appium-service.d.ts b/packages/wdio-appium-service/src/@types/appium-service.d.ts deleted file mode 100644 index 56d19157ca2..00000000000 --- a/packages/wdio-appium-service/src/@types/appium-service.d.ts +++ /dev/null @@ -1,51 +0,0 @@ -declare module WebdriverIO { - interface ServiceOption extends AppiumServiceConfig {} -} - -declare module WebDriver { - interface DesiredCapabilities extends AppiumSessionCapabilities {} -} - -type ArgValue = string | number | boolean | null - -type AppiumServerArguments = { - [capability: string]: any - /** - * Port to listen on - */ - port?: number | string - basePath?: string -} -interface AppiumSessionCapabilities { - /** - * Default session parameters - */ - port?: number - protocol?: string - hostname?: string - path?: string -} - -type KeyValueArgs = { - [key: string]: ArgValue -} - -interface AppiumServiceConfig { - /** - * Path where all logs from the Appium server should be stored. - */ - logPath?: string; - /** - * To use your own installation of Appium, e.g. globally installed, specify the command which should be started. - */ - command?: string; - /** - * Map of arguments for the Appium server, passed directly to `appium`. - */ - args?: AppiumServerArguments | Array -} - -type Config = { - outputDir?: string - [key: string]: any -} diff --git a/packages/wdio-appium-service/src/index.ts b/packages/wdio-appium-service/src/index.ts index 24693b1c651..5d54f951ae1 100644 --- a/packages/wdio-appium-service/src/index.ts +++ b/packages/wdio-appium-service/src/index.ts @@ -4,3 +4,12 @@ import AppiumLauncher from './launcher' export default class AppiumService {} export const launcher = AppiumLauncher + +export * from './types' +import { AppiumServiceConfig } from './types' + +declare global { + namespace WebdriverIO { + interface ServiceOption extends AppiumServiceConfig {} + } +} diff --git a/packages/wdio-appium-service/src/launcher.ts b/packages/wdio-appium-service/src/launcher.ts index c7a521fae6d..18d2ab0dc35 100644 --- a/packages/wdio-appium-service/src/launcher.ts +++ b/packages/wdio-appium-service/src/launcher.ts @@ -2,9 +2,12 @@ import logger from '@wdio/logger' import { ChildProcessByStdio, spawn } from 'child_process' import { createWriteStream, ensureFileSync } from 'fs-extra' import { promisify } from 'util' -import { getFilePath, formatCliArgs } from './utils' import { Readable } from 'stream' import { isCloudCapability } from '@wdio/config' +import type { Services, Capabilities, Options } from '@wdio/types' + +import { getFilePath, formatCliArgs } from './utils' +import type { AppiumServerArguments, AppiumServiceConfig } from './types' const log = logger('@wdio/appium-service') const DEFAULT_LOG_FILENAME = 'wdio-appium.log' @@ -16,32 +19,23 @@ const DEFAULT_CONNECTION = { path: '/' } -export default class AppiumLauncher implements WebdriverIO.ServiceInstance { +export default class AppiumLauncher implements Services.ServiceInstance { private readonly _logPath?: string - private readonly _appiumCliArgs: Array = [] - private readonly _capabilities: Array + private readonly _appiumCliArgs: string[] = [] private readonly _args: AppiumServerArguments private _command: string private _process?: ChildProcessByStdio constructor( - private _options: WebdriverIO.ServiceOption, - capabilities: - Array | {[capabilitiy: string]: WebDriver.DesiredCapabilities } = {}, - private _config?: Config + private _options: AppiumServiceConfig, + private _capabilities: Capabilities.RemoteCapabilities, + private _config?: Options.Testrunner ) { - /** - * Convert capability object to Array of capabilities - */ - this._capabilities = Array.isArray(capabilities) - ? capabilities - : Object.values(capabilities) - this._args = { basePath: DEFAULT_CONNECTION.path, ...(this._options.args || {}) } - this._logPath = _options.logPath || _config?.outputDir + this._logPath = _options.logPath || this._config?.outputDir this._command = this._getCommand(_options.command) } @@ -71,8 +65,26 @@ export default class AppiumLauncher implements WebdriverIO.ServiceInstance { * to Appium server */ private _setCapabilities() { + /** + * Multiremote sessions + */ + if (!Array.isArray(this._capabilities)) { + for (const [, capability] of Object.entries(this._capabilities)) { + const cap = (capability.capabilities as Capabilities.W3CCapabilities) || capability + const c = (cap as Capabilities.W3CCapabilities).alwaysMatch || cap + !isCloudCapability(c) && Object.assign( + capability, + DEFAULT_CONNECTION, + 'port' in this._args ? { port: this._args.port } : {}, + { path: this._args.basePath }, + { ...capability } + ) + } + return + } + this._capabilities.forEach( - (cap) => !isCloudCapability(cap) && Object.assign( + (cap) => !isCloudCapability((cap as Capabilities.W3CCapabilities).alwaysMatch || cap) && Object.assign( cap, DEFAULT_CONNECTION, 'port' in this._args ? { port: this._args.port } : {}, diff --git a/packages/wdio-appium-service/appium-service.d.ts b/packages/wdio-appium-service/src/types.ts similarity index 55% rename from packages/wdio-appium-service/appium-service.d.ts rename to packages/wdio-appium-service/src/types.ts index 56d19157ca2..e71e7e85109 100644 --- a/packages/wdio-appium-service/appium-service.d.ts +++ b/packages/wdio-appium-service/src/types.ts @@ -1,14 +1,4 @@ -declare module WebdriverIO { - interface ServiceOption extends AppiumServiceConfig {} -} - -declare module WebDriver { - interface DesiredCapabilities extends AppiumSessionCapabilities {} -} - -type ArgValue = string | number | boolean | null - -type AppiumServerArguments = { +export type AppiumServerArguments = { [capability: string]: any /** * Port to listen on @@ -16,7 +6,8 @@ type AppiumServerArguments = { port?: number | string basePath?: string } -interface AppiumSessionCapabilities { + +export interface AppiumSessionCapabilities { /** * Default session parameters */ @@ -26,26 +17,20 @@ interface AppiumSessionCapabilities { path?: string } -type KeyValueArgs = { - [key: string]: ArgValue -} - -interface AppiumServiceConfig { +export interface AppiumServiceConfig { /** * Path where all logs from the Appium server should be stored. */ - logPath?: string; + logPath?: string /** * To use your own installation of Appium, e.g. globally installed, specify the command which should be started. */ - command?: string; + command?: string /** * Map of arguments for the Appium server, passed directly to `appium`. */ args?: AppiumServerArguments | Array } -type Config = { - outputDir?: string - [key: string]: any -} +export type ArgValue = string | number | boolean | null +export type KeyValueArgs = { [key: string]: ArgValue } diff --git a/packages/wdio-appium-service/src/utils.ts b/packages/wdio-appium-service/src/utils.ts index a7c7b276b0f..a93bd90e8ea 100644 --- a/packages/wdio-appium-service/src/utils.ts +++ b/packages/wdio-appium-service/src/utils.ts @@ -1,6 +1,8 @@ import { basename, join, resolve } from 'path' import { paramCase } from 'param-case' +import { ArgValue, KeyValueArgs } from './types' + const FILE_EXTENSION_REGEX = /\.[0-9a-z]+$/i /** @@ -28,7 +30,7 @@ export function formatCliArgs(args: KeyValueArgs | ArgValue[]): string[] { const cliArgs = [] for (const key in args) { - let value: ArgValue | ArgValue[] = args[key] + let value: ArgValue | ArgValue[] = args[key] // If the value is false or null the argument is discarded if ((typeof value === 'boolean' && !value) || value === null) { continue diff --git a/packages/wdio-appium-service/tests/launcher.test.ts b/packages/wdio-appium-service/tests/launcher.test.ts index c30646460c3..8b1c953f67c 100644 --- a/packages/wdio-appium-service/tests/launcher.test.ts +++ b/packages/wdio-appium-service/tests/launcher.test.ts @@ -3,6 +3,7 @@ import childProcess from 'child_process' import fs from 'fs-extra' import { mocked } from 'ts-jest/utils' import path from 'path' +import type { Capabilities, Options } from '@wdio/types' jest.mock('child_process', () => ({ spawn: jest.fn(), @@ -81,8 +82,8 @@ describe('Appium launcher', () => { command:'path/to/my_custom_appium', args: { foo: 'bar' } } - const capabilities = [{ port: 1234 }] as WebDriver.DesiredCapabilities[] - const launcher = new AppiumLauncher(options, capabilities, {}) + const capabilities = [{ port: 1234, capabilities: [] }] as (Capabilities.DesiredCapabilities & Options.WebDriver)[] + const launcher = new AppiumLauncher(options, capabilities, {} as any) await launcher.onPrepare() expect(launcher['_process']).toBeInstanceOf(MockProcess) @@ -104,11 +105,11 @@ describe('Appium launcher', () => { command: 'path/to/my_custom_appium', args: { foo: 'bar' } } - const capabilities = { - browserA: { port: 1234 } as WebDriver.DesiredCapabilities, - browserB: {} as WebDriver.DesiredCapabilities + const capabilities: Capabilities.MultiRemoteCapabilities = { + browserA: { port: 1234, capabilities: {} }, + browserB: { capabilities: {} } } - const launcher = new AppiumLauncher(options, capabilities, {}) + const launcher = new AppiumLauncher(options, capabilities, {} as any) await launcher.onPrepare() expect(capabilities.browserA.protocol).toBe('http') expect(capabilities.browserA.hostname).toBe('localhost') @@ -126,11 +127,11 @@ describe('Appium launcher', () => { args : { foo : 'foo' }, installArgs : { bar : 'bar' }, } - const capabilities = { - browserA: { port: 1234 } as WebDriver.DesiredCapabilities, - browserB: { port: 4321, capabilities: { 'bstack:options': {} } } as WebDriver.DesiredCapabilities + const capabilities: Capabilities.MultiRemoteCapabilities = { + browserA: { port: 1234, capabilities: {} }, + browserB: { port: 4321, capabilities: { 'bstack:options': {} } } } - const launcher = new AppiumLauncher(options, capabilities, {}) + const launcher = new AppiumLauncher(options, capabilities, {} as any) launcher['_redirectLogStream'] = jest.fn() await launcher.onPrepare() expect(capabilities.browserA.protocol).toBe('http') @@ -149,8 +150,8 @@ describe('Appium launcher', () => { command: 'path/to/my_custom_appium', args: { foo: 'bar', port: 1234 } } - const capabilities = [{} as WebDriver.DesiredCapabilities] - const launcher = new AppiumLauncher(options, capabilities, {}) + const capabilities = [{} as Capabilities.DesiredCapabilities] + const launcher = new AppiumLauncher(options, capabilities, {} as any) launcher['_startAppium'] = jest.fn().mockImplementation( (cmd, args, cb) => cb(null, new MockProcess())) await launcher.onPrepare() @@ -174,8 +175,8 @@ describe('Appium launcher', () => { command: 'path/to/my_custom_appium', args: { foo: 'bar', port: 1234, basePath: '/foo/bar' } } - const capabilities = [{ port: 4321 } as WebDriver.DesiredCapabilities] - const launcher = new AppiumLauncher(options, capabilities, {}) + const capabilities = [{ port: 4321 } as Capabilities.DesiredCapabilities] + const launcher = new AppiumLauncher(options, capabilities, {} as any) launcher['_startAppium'] = jest.fn().mockImplementation( (cmd, args, cb) => cb(null, new MockProcess())) await launcher.onPrepare() @@ -203,7 +204,7 @@ describe('Appium launcher', () => { logPath: './', command: 'path/to/my_custom_appium', args: { foo: 'bar' } - }, [], {}) + }, [], {} as any) await launcher.onPrepare() expect(launcher['_command']).toBe('cmd') @@ -219,7 +220,7 @@ describe('Appium launcher', () => { logPath: './', command: 'path/to/my_custom_appium', args: { foo: 'bar' } - }, [], {}) + }, [], {} as any) await launcher.onPrepare() expect(launcher['_command']).toBe('path/to/my_custom_appium') @@ -235,7 +236,7 @@ describe('Appium launcher', () => { logPath: './', command: 'path/to/my_custom_appium', args: { foo: 'bar' } - }, [], {}) + }, [], {} as any) await launcher.onPrepare() expect(launcher['_command']).toBe('path/to/my_custom_appium') @@ -243,7 +244,7 @@ describe('Appium launcher', () => { }) test('should set correct config properties when empty', async () => { - const launcher = new AppiumLauncher({}, [], {}) + const launcher = new AppiumLauncher({}, [], {} as any) await launcher.onPrepare() expect(launcher['_logPath']).toBe(undefined) @@ -255,12 +256,12 @@ describe('Appium launcher', () => { }) test('should start Appium', async () => { - const launcher = new AppiumLauncher({ args: { superspeed: true } }, [], {}) + const launcher = new AppiumLauncher({ args: { superspeed: true } }, [], {} as any) await launcher.onPrepare() }) test('should fail if Appium exits', async () => { - const launcher = new AppiumLauncher({}, [], {}) + const launcher = new AppiumLauncher({}, [], {} as any) childProcessMock.spawn.mockReturnValue(new MockFailingProcess(1) as unknown as childProcess.ChildProcess) let error @@ -274,7 +275,7 @@ describe('Appium launcher', () => { }) test('should fail and error message if Appium already runs', async () => { - const launcher = new AppiumLauncher({}, [], {}) + const launcher = new AppiumLauncher({}, [], {} as any) childProcessMock.spawn.mockReturnValue(new MockFailingProcess(2) as unknown as childProcess.ChildProcess) let error @@ -289,7 +290,7 @@ describe('Appium launcher', () => { }) test('should fail with Appium error message', async () => { - const launcher = new AppiumLauncher({}, [], {}) + const launcher = new AppiumLauncher({}, [], {} as any) childProcessMock.spawn.mockReturnValue(new MockCustomFailingProcess(2) as unknown as childProcess.ChildProcess) let error @@ -305,7 +306,7 @@ describe('Appium launcher', () => { describe('onComplete', () => { test('should call process.kill', async () => { - const launcher = new AppiumLauncher({}, [], {}) + const launcher = new AppiumLauncher({}, [], {} as any) await launcher.onPrepare() launcher['_process']!.kill = jest.fn() launcher.onComplete() @@ -313,7 +314,7 @@ describe('Appium launcher', () => { }) test('should not call process.kill', () => { - const launcher = new AppiumLauncher({}, [], {}) + const launcher = new AppiumLauncher({}, [], {} as any) expect(launcher['_process']).toBe(undefined) launcher.onComplete() expect(launcher['_process']).toBe(undefined) @@ -322,14 +323,14 @@ describe('Appium launcher', () => { describe('_redirectLogStream', () => { test('should not write output to file', async () => { - const launcher = new AppiumLauncher({}, [], {}) + const launcher = new AppiumLauncher({}, [], {} as any) launcher['_redirectLogStream'] = jest.fn() await launcher.onPrepare() expect(launcher['_redirectLogStream']).not.toBeCalled() }) test('should write output to file', async () => { - const launcher = new AppiumLauncher({ logPath: './' }, [], {}) + const launcher = new AppiumLauncher({ logPath: './' }, [], {} as any) await launcher.onPrepare() expect(fsMocked.createWriteStream.mock.calls[0][0]).toBe('/some/file/path') diff --git a/packages/wdio-appium-service/tsconfig.json b/packages/wdio-appium-service/tsconfig.json index a32652092d3..d660189bdb9 100644 --- a/packages/wdio-appium-service/tsconfig.json +++ b/packages/wdio-appium-service/tsconfig.json @@ -6,6 +6,7 @@ "rootDir": "./src" }, "include": [ - "src/**/*" + "src/**/*", + "../../@types" ] } diff --git a/packages/wdio-appium-service/tsconfig.prod.json b/packages/wdio-appium-service/tsconfig.prod.json index c7e2194a8b4..2a0e7e84140 100644 --- a/packages/wdio-appium-service/tsconfig.prod.json +++ b/packages/wdio-appium-service/tsconfig.prod.json @@ -6,6 +6,7 @@ "rootDir": "./src" }, "include": [ - "src/**/*" + "src/**/*", + "../../@types" ] } diff --git a/packages/wdio-applitools-service/applitools-service.d.ts b/packages/wdio-applitools-service/applitools-service.d.ts deleted file mode 100644 index 76720c30e03..00000000000 --- a/packages/wdio-applitools-service/applitools-service.d.ts +++ /dev/null @@ -1,53 +0,0 @@ -/// -declare namespace WebdriverIO { - interface ServiceOption extends ApplitoolsConfig {} - interface Browser extends ApplitoolsBrowser {} -} - -interface ProxySettings { - url: string | boolean; - username: string; - password: string; - isHttpOnly: boolean; -} - -interface ApplitoolsConfig { - /** - * Applitools API key to be used. Can be passed via wdio config or via environment - * variable `APPLITOOLS_KEY` - */ - key?: string; - /** - * Applitools server URL to be used. - */ - serverUrl?: string; - /** - * Viewport with which the screenshots should be taken. - */ - viewport?: { - width?: number; - height?: number; - }; - /** - * Use proxy for http/https connections with Applitools. - */ - proxy?: ProxySettings; -} - -interface ApplitoolsBrowser { - takeSnapshot(title: string): void; - takeRegionSnapshot( - title: string, - region: Eyes.Check.Region | WebdriverIO.Element | string, - frame?: WebdriverIO.Element | string - ): void; -} - -interface ApplitoolsBrowserAsync { - takeSnapshot(title: string): Promise; - takeRegionSnapshot( - title: string, - region: Eyes.Check.Region | WebdriverIO.Element | string, - frame?: WebdriverIO.Element | string - ): Promise; -} diff --git a/packages/wdio-applitools-service/package.json b/packages/wdio-applitools-service/package.json index 87faa421654..fed2c746b90 100644 --- a/packages/wdio-applitools-service/package.json +++ b/packages/wdio-applitools-service/package.json @@ -27,7 +27,9 @@ }, "dependencies": { "@applitools/eyes-webdriverio": "^5.8.4", - "@wdio/logger": "6.10.10" + "@wdio/logger": "6.10.10", + "@wdio/types": "^6.10.6", + "webdriverio": "^6.11.3" }, "peerDependencies": { "@wdio/cli": "^6.0.1" @@ -35,5 +37,5 @@ "publishConfig": { "access": "public" }, - "types": "applitools-service.d.ts" + "types": "./build/index.d.ts" } diff --git a/packages/wdio-applitools-service/src/@types/applitools-service.d.ts b/packages/wdio-applitools-service/src/@types/applitools-service.d.ts deleted file mode 120000 index 49db1553eb5..00000000000 --- a/packages/wdio-applitools-service/src/@types/applitools-service.d.ts +++ /dev/null @@ -1 +0,0 @@ -../../applitools-service.d.ts \ No newline at end of file diff --git a/packages/wdio-applitools-service/src/index.ts b/packages/wdio-applitools-service/src/index.ts index 6702d99624f..de0546ca85d 100644 --- a/packages/wdio-applitools-service/src/index.ts +++ b/packages/wdio-applitools-service/src/index.ts @@ -1,5 +1,9 @@ import logger from '@wdio/logger' import { Eyes, Target } from '@applitools/eyes-webdriverio' +import type { Services, Capabilities, FunctionProperties } from '@wdio/types' +import type { Browser } from 'webdriverio' + +import { ApplitoolsConfig, Frame, Region } from './types' const log = logger('@wdio/applitools-service') @@ -8,11 +12,11 @@ const DEFAULT_VIEWPORT = { height: 900 } -export default class ApplitoolsService implements WebdriverIO.ServiceInstance { +export default class ApplitoolsService implements Services.ServiceInstance { private _isConfigured: boolean = false private _viewport: Required private _eyes = new Eyes() - private _browser?: WebdriverIO.BrowserObject | WebdriverIO.MultiRemoteBrowser + private _browser?: Browser<'async'> constructor(private _options: ApplitoolsConfig) {} @@ -37,8 +41,8 @@ export default class ApplitoolsService implements WebdriverIO.ServiceInstance { this._isConfigured = true this._eyes.setApiKey(key) - if (this._options.proxy) { - this._eyes.setProxy(this._options.proxy) + if (this._options.eyesProxy) { + this._eyes.setProxy(this._options.eyesProxy) } this._viewport = Object.assign({ ...DEFAULT_VIEWPORT }, this._options.viewport) @@ -48,9 +52,9 @@ export default class ApplitoolsService implements WebdriverIO.ServiceInstance { * set custom commands */ before( - caps: WebDriver.Capabilities, + caps: Capabilities.RemoteCapability, specs: string[], - browser: WebdriverIO.BrowserObject | WebdriverIO.MultiRemoteBrowserObject + browser: Browser<'async'> ) { this._browser = browser @@ -58,26 +62,29 @@ export default class ApplitoolsService implements WebdriverIO.ServiceInstance { return } - this._browser.addCommand('takeSnapshot', (title: string) => { - if (!title) { - throw new Error('A title for the Applitools snapshot is missing') - } - - return this._eyes.check(title, Target.window()) - }) - - this._browser.addCommand('takeRegionSnapshot', (title: string, region: Region, frame: Frame) => { - if (!title) { - throw new Error('A title for the Applitools snapshot is missing') - } - if (!region || region === null) { - throw new Error('A region for the Applitools snapshot is missing') - } - if (!frame) { - return this._eyes.check(title, Target.region(region)) - } - return this._eyes.check(title, Target.region(region, frame)) - }) + this._browser.addCommand('takeSnapshot', this._takeSnapshot.bind(this)) + this._browser.addCommand('takeRegionSnapshot', this._takeRegionSnapshot.bind(this)) + } + + _takeSnapshot (title: string): void { + if (!title) { + throw new Error('A title for the Applitools snapshot is missing') + } + + return this._eyes.check(title, Target.window()) + } + + _takeRegionSnapshot (title: string, region: Region, frame: Frame): void { + if (!title) { + throw new Error('A title for the Applitools snapshot is missing') + } + if (!region || region === null) { + throw new Error('A region for the Applitools snapshot is missing') + } + if (!frame) { + return this._eyes.check(title, Target.region(region)) + } + return this._eyes.check(title, Target.region(region, frame)) } beforeTest(test: { title: string, parent: string }) { @@ -105,3 +112,27 @@ export default class ApplitoolsService implements WebdriverIO.ServiceInstance { this._browser.call(this._eyes.abortIfNotClosed.bind(this._eyes)) } } + +export * from './types' + +type ServiceCommands = FunctionProperties +interface BrowserExtension { + takeSnapshot: ServiceCommands['_takeSnapshot'] + takeRegionSnapshot: ServiceCommands['_takeRegionSnapshot'] +} + +declare global { + namespace WebdriverIO { + interface ServiceOption extends ApplitoolsConfig {} + } + + namespace WebdriverIOAsync { + interface Browser extends BrowserExtension { } + interface MultiRemoteBrowser extends BrowserExtension { } + } + + namespace WebdriverIOSync { + interface Browser extends BrowserExtension { } + interface MultiRemoteBrowser extends BrowserExtension { } + } +} diff --git a/packages/wdio-applitools-service/src/types.ts b/packages/wdio-applitools-service/src/types.ts new file mode 100644 index 00000000000..36705c2ccbf --- /dev/null +++ b/packages/wdio-applitools-service/src/types.ts @@ -0,0 +1,37 @@ +interface ProxySettings { + url: string | boolean; + username: string; + password: string; + isHttpOnly: boolean; +} + +export interface ApplitoolsConfig { + /** + * Applitools API key to be used. Can be passed via wdio config or via environment + * variable `APPLITOOLS_KEY` + */ + key?: string; + /** + * Applitools server URL to be used. + */ + serverUrl?: string; + /** + * Viewport with which the screenshots should be taken. + */ + viewport?: { + width?: number; + height?: number; + }; + /** + * Use proxy for http/https connections with Applitools. + */ + eyesProxy?: ProxySettings; +} + +export type Region = { + top: number + left: number + width: number + height: number +} | Element | string; +export type Frame = Element | string; diff --git a/packages/wdio-applitools-service/tests/service.test.ts b/packages/wdio-applitools-service/tests/service.test.ts index 3a18616189f..8faa91460f4 100644 --- a/packages/wdio-applitools-service/tests/service.test.ts +++ b/packages/wdio-applitools-service/tests/service.test.ts @@ -1,6 +1,11 @@ +import type { Capabilities } from '@wdio/types' +import type { ApplitoolsBrowserAsync } from '../src/types' + import ApplitoolsService from '../src' -const caps: WebDriver.Capabilities = { +const expect = global.expect as unknown as jest.Expect + +const caps: Capabilities.Capabilities = { browserName: 'chrome' } @@ -17,7 +22,7 @@ class BrowserMock { } function getBrowser () { - return new BrowserMock() as unknown as WebdriverIO.BrowserObject + return new BrowserMock() as unknown as ApplitoolsBrowserAsync } describe('wdio-applitools-service', () => { @@ -74,7 +79,7 @@ describe('wdio-applitools-service', () => { } const service = new ApplitoolsService({ key: 'foobar', - proxy: proxyOptions + eyesProxy: proxyOptions }) service.beforeSession() diff --git a/packages/wdio-applitools-service/tsconfig.json b/packages/wdio-applitools-service/tsconfig.json index 8210bac443e..d660189bdb9 100644 --- a/packages/wdio-applitools-service/tsconfig.json +++ b/packages/wdio-applitools-service/tsconfig.json @@ -3,10 +3,10 @@ "compilerOptions": { "baseUrl": ".", "outDir": "./build", - "rootDir": "./src", - "types": ["webdriver", "webdriverio", "@wdio/logger", "jest"] + "rootDir": "./src" }, "include": [ - "src/**/*" + "src/**/*", + "../../@types" ] } diff --git a/packages/wdio-applitools-service/tsconfig.prod.json b/packages/wdio-applitools-service/tsconfig.prod.json index c91ac991bae..2a0e7e84140 100644 --- a/packages/wdio-applitools-service/tsconfig.prod.json +++ b/packages/wdio-applitools-service/tsconfig.prod.json @@ -3,10 +3,10 @@ "compilerOptions": { "baseUrl": ".", "outDir": "./build", - "rootDir": "./src", - "types": ["webdriver", "webdriverio", "@wdio/logger", "jest"] + "rootDir": "./src" }, "include": [ - "src/**/*" + "src/**/*", + "../../@types" ] } diff --git a/packages/wdio-browserstack-service/package.json b/packages/wdio-browserstack-service/package.json index 5b4e426b03c..4cf7e81de53 100644 --- a/packages/wdio-browserstack-service/package.json +++ b/packages/wdio-browserstack-service/package.json @@ -24,8 +24,10 @@ }, "dependencies": { "@wdio/logger": "6.10.10", + "@wdio/types": "^6.10.6", "browserstack-local": "^1.4.5", - "got": "^11.0.2" + "got": "^11.0.2", + "webdriverio": "^6.11.3" }, "peerDependencies": { "@wdio/cli": "^6.0.1" @@ -33,5 +35,5 @@ "publishConfig": { "access": "public" }, - "types": "browserstack-service.d.ts" + "types": "./build/index.d.ts" } diff --git a/packages/wdio-browserstack-service/src/constants.ts b/packages/wdio-browserstack-service/src/constants.ts index 0df0e2b52ee..44c38d2ddcf 100644 --- a/packages/wdio-browserstack-service/src/constants.ts +++ b/packages/wdio-browserstack-service/src/constants.ts @@ -7,4 +7,6 @@ export const BROWSER_DESCRIPTION = [ 'browser', 'browserVersion', 'browser_version' -] +] as const + +export const CUCUMBER_STATUS_MAP = ['UNKNOWN', 'PASSED', 'SKIPPED', 'PENDING', 'UNDEFINED', 'AMBIGUOUS', 'FAILED'] diff --git a/packages/wdio-browserstack-service/src/index.ts b/packages/wdio-browserstack-service/src/index.ts index 47de76ccfdd..dcc5bc6a12b 100644 --- a/packages/wdio-browserstack-service/src/index.ts +++ b/packages/wdio-browserstack-service/src/index.ts @@ -2,6 +2,14 @@ import BrowserstackLauncher from './launcher' import BrowserstackService from './service' +import type { BrowserstackConfig } from './types' export default BrowserstackService export const launcher = BrowserstackLauncher +export * from './types' + +declare global { + namespace WebdriverIO { + interface ServiceOption extends BrowserstackConfig {} + } +} diff --git a/packages/wdio-browserstack-service/src/launcher.ts b/packages/wdio-browserstack-service/src/launcher.ts index e5dc3693f0f..d49f9188dca 100644 --- a/packages/wdio-browserstack-service/src/launcher.ts +++ b/packages/wdio-browserstack-service/src/launcher.ts @@ -1,8 +1,11 @@ import { promisify } from 'util' import { performance, PerformanceObserver } from 'perf_hooks' -import BrowserstackLocalLauncher from 'browserstack-local' +import * as BrowserstackLocalLauncher from 'browserstack-local' import logger from '@wdio/logger' +import type { Capabilities, Services, Options } from '@wdio/types' + +import { BrowserstackConfig } from './types' const log = logger('@wdio/browserstack-service') @@ -11,37 +14,43 @@ type BrowserstackLocal = BrowserstackLocalLauncher.Local & { stop(callback: (err?: any) => void): void; } -type Capabilities = (WebDriver.Capabilities | WebDriver.Capabilities[]) - -export default class BrowserstackLauncherService implements WebdriverIO.ServiceInstance { - options: BrowserstackConfig - config: WebdriverIO.Config +export default class BrowserstackLauncherService implements Services.ServiceInstance { browserstackLocal?: BrowserstackLocal - constructor (options: BrowserstackConfig, capabilities: Capabilities, config: WebdriverIO.Config) { - this.options = options - this.config = config - } - onPrepare (config?: WebdriverIO.Config, capabilities?: Capabilities) { - if (!this.options.browserstackLocal) { + constructor ( + private _options: BrowserstackConfig, + capabilities: Capabilities.RemoteCapability, + private _config: Options.Testrunner + ) {} + + onPrepare (config?: Options.Testrunner, capabilities?: Capabilities.RemoteCapabilities) { + if (!this._options.browserstackLocal) { return log.info('browserstackLocal is not enabled - skipping...') } const opts = { - key: this.config.key, + key: this._config.key, forcelocal: true, onlyAutomate: true, - ...this.options.opts + ...this._options.opts } this.browserstackLocal = new BrowserstackLocalLauncher.Local() if (Array.isArray(capabilities)) { - capabilities.forEach(capability => { - capability['browserstack.local'] = true + capabilities.forEach((capability: Capabilities.DesiredCapabilities) => { + if (!capability['bstack:options']) { + capability['bstack:options'] = {} + } + capability['bstack:options'].local = true }) } else if (typeof capabilities === 'object') { - capabilities['browserstack.local'] = true + Object.entries(capabilities as Capabilities.MultiRemoteCapabilities).forEach(([, caps]) => { + if (!(caps.capabilities as Capabilities.Capabilities)['bstack:options']) { + (caps.capabilities as Capabilities.Capabilities)['bstack:options'] = {} + } + (caps.capabilities as Capabilities.Capabilities)['bstack:options']!.local = true + }) } else { throw TypeError('Capabilities should be an object or Array!') } @@ -81,13 +90,13 @@ export default class BrowserstackLauncherService implements WebdriverIO.ServiceI return } - if (this.options.forcedStop) { + if (this._options.forcedStop) { return process.kill(this.browserstackLocal.pid as number) } let timer: NodeJS.Timeout return Promise.race([ - new Promise((resolve, reject) => { + new Promise((resolve, reject) => { this.browserstackLocal?.stop((err: Error) => { if (err) { return reject(err) diff --git a/packages/wdio-browserstack-service/src/service.ts b/packages/wdio-browserstack-service/src/service.ts index 89a8c576a9d..26586480400 100644 --- a/packages/wdio-browserstack-service/src/service.ts +++ b/packages/wdio-browserstack-service/src/service.ts @@ -1,33 +1,51 @@ import logger from '@wdio/logger' import got from 'got' -import { Browser, Capabilities, Context, Feature, MultiRemoteAction, Pickle, SessionResponse } from './types' +import type { Services, Capabilities, Options, Frameworks } from '@wdio/types' +import type { Browser, MultiRemoteBrowser } from 'webdriverio' import { getBrowserDescription, getBrowserCapabilities, isBrowserstackCapability } from './util' +import { BrowserstackConfig, MultiRemoteAction, SessionResponse } from './types' +import { CUCUMBER_STATUS_MAP } from './constants' const log = logger('@wdio/browserstack-service') -export default class BrowserstackService implements WebdriverIO.ServiceInstance { - private _sessionBaseUrl: string = 'https://api.browserstack.com/automate/sessions'; - private _failReasons: string[] = []; - private _scenariosThatRan: string[] = []; - private _failureStatuses: string[] = ['failed', 'ambiguous', 'undefined', 'unknown']; - private _browser?: Browser; - private _fullTitle?: string; - constructor (private _options: BrowserstackConfig = {}, private _caps: Capabilities, private _config: WebdriverIO.Config) { +export default class BrowserstackService implements Services.ServiceInstance { + private _sessionBaseUrl = 'https://api.browserstack.com/automate/sessions' + private _failReasons: string[] = [] + private _scenariosThatRan: string[] = [] + private _failureStatuses: string[] = ['failed', 'ambiguous', 'undefined', 'unknown'] + private _browser?: Browser<'async'> | MultiRemoteBrowser<'async'> + private _fullTitle?: string + + constructor ( + private _options: BrowserstackConfig, + private _caps: Capabilities.RemoteCapability, + private _config: Options.Testrunner + ) { // Cucumber specific - const strict = Boolean(_config.cucumberOpts && _config.cucumberOpts.strict) + const strict = Boolean(this._config.cucumberOpts && this._config.cucumberOpts.strict) // See https://github.com/cucumber/cucumber-js/blob/master/src/runtime/index.ts#L136 if (strict) { this._failureStatuses.push('pending') } } + _updateCaps (fn: (caps: Capabilities.Capabilities | Capabilities.DesiredCapabilities) => void) { + const multiRemoteCap = this._caps as Capabilities.MultiRemoteCapabilities + + if (multiRemoteCap.capabilities) { + return Object.entries(multiRemoteCap).forEach(([, caps]) => fn(caps.capabilities as Capabilities.Capabilities)) + } + + return fn(this._caps as Capabilities.Capabilities) + } + /** * if no user and key is specified even though a browserstack service was * provided set user and key with values so that the session request * will fail */ - beforeSession (config: WebdriverIO.Config) { + beforeSession (config: Options.Testrunner) { if (!config.user) { config.user = 'NotSetUser' } @@ -39,12 +57,11 @@ export default class BrowserstackService implements WebdriverIO.ServiceInstance this._config.key = config.key } - before(caps: Capabilities, specs: string[], browser: Browser) { + before(caps: Capabilities.RemoteCapability, specs: string[], browser: Browser<'async'> | MultiRemoteBrowser<'async'>) { this._browser = browser // Ensure capabilities are not null in case of multiremote - const capabilities = this._browser.capabilities || {} - if (capabilities.app || this._caps.app) { + if ((this._browser.capabilities as Capabilities.DesiredCapabilities).app || (this._caps as Capabilities.DesiredCapabilities).app) { this._sessionBaseUrl = 'https://api-cloud.browserstack.com/app-automate/sessions' } @@ -53,17 +70,17 @@ export default class BrowserstackService implements WebdriverIO.ServiceInstance return this._printSessionURL() } - beforeSuite (suite: WebdriverIO.Suite) { + beforeSuite (suite: Frameworks.Suite) { this._fullTitle = suite.title return this._updateJob({ name: this._fullTitle }) } - beforeFeature(uri: string, feature: Feature) { - this._fullTitle = feature.document.feature.name + beforeFeature(uri: unknown, feature: { name: string }) { + this._fullTitle = feature.name return this._updateJob({ name: this._fullTitle }) } - afterTest(test: WebdriverIO.Test, context: Context, results: WebdriverIO.TestResult) { + afterTest(test: Frameworks.Test, context: never, results: Frameworks.TestResult) { const { error, passed } = results this._fullTitle = ( @@ -82,7 +99,7 @@ export default class BrowserstackService implements WebdriverIO.ServiceInstance } } - after(result: number) { + after (result: number) { // For Cucumber: Checks scenarios that ran (i.e. not skipped) on the session // Only 1 Scenario ran and option enabled => Redefine session name to Scenario's name if (this._options.preferScenarioName && this._scenariosThatRan.length === 1){ @@ -101,18 +118,20 @@ export default class BrowserstackService implements WebdriverIO.ServiceInstance /** * For CucumberJS */ - - afterScenario(uri: string, feature: Feature, pickle: Pickle, results: WebdriverIO.TestResult) { - let { exception, status } = results - - if (status !== 'skipped') { - this._scenariosThatRan.push(pickle.name) + afterScenario (world: Frameworks.World) { + const status = CUCUMBER_STATUS_MAP[world.result?.status || 0].toLowerCase() + if (status === 'skipped') { + this._scenariosThatRan.push(world.pickle.name || 'unknown pickle name') } if (this._failureStatuses.includes(status)) { - exception = exception || (status === 'pending' - ? `Some steps/hooks are pending for scenario "${pickle.name}"` - : 'Unknown Error') + const exception = ( + (world.result && world.result.message) || + (status === 'pending' + ? `Some steps/hooks are pending for scenario "${world.pickle.name}"` + : 'Unknown Error' + ) + ) this._failReasons.push(exception) } @@ -129,8 +148,8 @@ export default class BrowserstackService implements WebdriverIO.ServiceInstance if (!this._browser.isMultiremote) { log.info(`Update (reloaded) job with sessionId ${oldSessionId}, ${status}`) } else { - const browserName = this._browser.instances.filter( - (browserName) => this._browser && this._browser[browserName].sessionId === newSessionId)[0] + const browserName = (this._browser as MultiRemoteBrowser<'async'>).instances.filter( + (browserName) => this._browser && (this._browser as MultiRemoteBrowser<'async'>)[browserName].sessionId === newSessionId)[0] log.info(`Update (reloaded) multiremote job for browser "${browserName}" and sessionId ${oldSessionId}, ${status}`) } @@ -145,8 +164,8 @@ export default class BrowserstackService implements WebdriverIO.ServiceInstance await this._printSessionURL() } - _updateJob(requestBody: any) { - return this._multiRemoteAction((sessionId, browserName) => { + _updateJob (requestBody: any) { + return this._multiRemoteAction((sessionId: string, browserName: string) => { log.info(browserName ? `Update multiremote job for browser "${browserName}" and sessionId ${sessionId}` : `Update job with sessionId ${sessionId}` @@ -155,7 +174,7 @@ export default class BrowserstackService implements WebdriverIO.ServiceInstance }) } - _multiRemoteAction(action: MultiRemoteAction) { + _multiRemoteAction (action: MultiRemoteAction) { const { _browser } = this if (!_browser) { return Promise.resolve() @@ -167,8 +186,8 @@ export default class BrowserstackService implements WebdriverIO.ServiceInstance return Promise.all(_browser.instances .filter(browserName => { - const cap = getBrowserCapabilities(_browser, this._caps, browserName) - return isBrowserstackCapability(cap as Capabilities) + const cap = getBrowserCapabilities(_browser, (this._caps as Capabilities.MultiRemoteCapabilities), browserName) + return isBrowserstackCapability(cap) }) .map((browserName: string) => ( action(_browser[browserName].sessionId, browserName) @@ -199,8 +218,12 @@ export default class BrowserstackService implements WebdriverIO.ServiceInstance responseType: 'json' }) - const capabilities = this._browser && getBrowserCapabilities(this._browser, this._caps, browserName) - const browserString = getBrowserDescription(capabilities as Capabilities) + if (!this._browser) { + return + } + + const capabilities = getBrowserCapabilities(this._browser, this._caps, browserName) + const browserString = getBrowserDescription(capabilities) log.info(`${browserString} session: ${response.body.automation_session.browser_url}`) }) } diff --git a/packages/wdio-browserstack-service/src/types.ts b/packages/wdio-browserstack-service/src/types.ts index b32d603d314..9a6615b7bd1 100644 --- a/packages/wdio-browserstack-service/src/types.ts +++ b/packages/wdio-browserstack-service/src/types.ts @@ -1,25 +1,3 @@ -import { GotRequestFunction } from 'got' - -export type Capabilities = WebDriver.Capabilities & WebdriverIO.MultiRemoteCapabilities; - -export type Browser = WebdriverIO.BrowserObject & WebdriverIO.MultiRemoteBrowserObject; - -export type Context = any; - -export type MultiRemoteAction = (sessionId: string, browserName?: string) => ReturnType | Promise; - -export interface Feature { - document: { - feature: { - name: string; - } - } -} - -export interface Pickle { - name: string; -} - export interface SessionResponse { // eslint-disable-next-line camelcase automation_session: { @@ -27,3 +5,29 @@ export interface SessionResponse { browser_url: string } } + +export type MultiRemoteAction = (sessionId: string, browserName?: string) => Promise; + +export interface BrowserstackConfig { + /** + * Set this to true to enable routing connections from Browserstack cloud through your computer. + * You will also need to set `browserstack.local` to true in browser capabilities. + */ + browserstackLocal?: boolean; + /** + * Cucumber only. Set this to true to enable updating the session name to the Scenario name if only + * a single Scenario was ran. Useful when running in parallel + * with [wdio-cucumber-parallel-execution](https://github.com/SimitTomar/wdio-cucumber-parallel-execution). + */ + preferScenarioName?: boolean; + /** + * Set this to true to kill the browserstack process on complete, without waiting for the + * browserstack stop callback to be called. This is experimental and should not be used by all. + */ + forcedStop?: boolean; + /** + * Specified optional will be passed down to BrowserstackLocal. See this list for details: + * https://stackoverflow.com/questions/39040108/import-class-in-definition-file-d-ts + */ + opts?: Partial +} diff --git a/packages/wdio-browserstack-service/src/util.ts b/packages/wdio-browserstack-service/src/util.ts index 2c9bed37633..21326e356d4 100644 --- a/packages/wdio-browserstack-service/src/util.ts +++ b/packages/wdio-browserstack-service/src/util.ts @@ -1,22 +1,24 @@ +import type { Browser, MultiRemoteBrowser } from 'webdriverio' +import type { Capabilities } from '@wdio/types' + import { BROWSER_DESCRIPTION } from './constants' -import { Browser, Capabilities } from './types' /** * get browser description for Browserstack service * @param cap browser capablities */ -export function getBrowserDescription(cap: Capabilities) { +export function getBrowserDescription(cap: Capabilities.DesiredCapabilities) { cap = cap || {} if (cap['bstack:options']) { - cap = { ...cap, ...cap['bstack:options'] } as Capabilities + cap = { ...cap, ...cap['bstack:options'] } as Capabilities.DesiredCapabilities } /** * These keys describe the browser the test was run on */ return BROWSER_DESCRIPTION - .map(k => cap[k]) - .filter(v => !!v) + .map((k: keyof Capabilities.DesiredCapabilities) => cap[k]) + .filter(Boolean) .join(' ') } @@ -26,20 +28,21 @@ export function getBrowserDescription(cap: Capabilities) { * @param caps browser capbilities object. In case of multiremote, the object itself should have a property named 'capabilities' * @param browserName browser name in case of multiremote */ -export function getBrowserCapabilities(browser: Browser, caps: Capabilities = {}, browserName?: string) { +export function getBrowserCapabilities(browser: Browser<'async'> | MultiRemoteBrowser<'async'>, caps?: Capabilities.RemoteCapability, browserName?: string) { if (!browser.isMultiremote) { return { ...browser.capabilities, ...caps } } + const multiCaps = caps as Capabilities.MultiRemoteCapabilities const globalCap = browserName && browser[browserName] ? browser[browserName].capabilities : {} - const cap = browserName && caps[browserName] ? caps[browserName].capabilities : {} - return { ...globalCap, ...cap } + const cap = browserName && multiCaps[browserName] ? multiCaps[browserName].capabilities : {} + return { ...globalCap, ...cap } as Capabilities.Capabilities } /** * check for browserstack W3C capabilities. Does not support legacy capabilities * @param cap browser capabilities */ -export function isBrowserstackCapability(cap?: Capabilities) { +export function isBrowserstackCapability(cap?: Capabilities.Capabilities) { return Boolean(cap && cap['bstack:options']) } diff --git a/packages/wdio-browserstack-service/tests/launcher.test.ts b/packages/wdio-browserstack-service/tests/launcher.test.ts index 3c2eab9700a..1dbacbed27b 100644 --- a/packages/wdio-browserstack-service/tests/launcher.test.ts +++ b/packages/wdio-browserstack-service/tests/launcher.test.ts @@ -1,7 +1,11 @@ -import BrowserstackLauncher from '../src/launcher' import Browserstack from 'browserstack-local' import logger from '@wdio/logger' +import BrowserstackLauncher from '../src/launcher' +import { BrowserstackConfig } from '../src/types' + +const expect = global.expect as unknown as jest.Expect + const log = logger('test') const error = new Error('I\'m an error!') @@ -14,14 +18,16 @@ describe('onPrepare', () => { const caps: any = [{}] const config = { user: 'foobaruser', - key: '12345' + key: '12345', + capabilities: [] } const logInfoSpy = jest.spyOn(log, 'info').mockImplementation((string) => string) it('should not call local if browserstackLocal is undefined', () => { const service = new BrowserstackLauncher({}, caps, { user: 'foobaruser', - key: '12345' + key: '12345', + capabilities: [] }) service.onPrepare() @@ -35,6 +41,7 @@ describe('onPrepare', () => { }, caps, { user: 'foobaruser', key: '12345', + capabilities: [] }) service.onPrepare() @@ -48,12 +55,12 @@ describe('onPrepare', () => { expect(service.browserstackLocal).toBeDefined() }) - it('should add the "browserstack.local" property to a single capability', async () => { + it('should add the "browserstack.local" property to a multiremote capability', async () => { const service = new BrowserstackLauncher(options, caps, config) - const capabilities = {} + const capabilities = { chromeBrowser: { capabilities: {} } } await service.onPrepare(config, capabilities) - expect(capabilities).toEqual({ 'browserstack.local': true }) + expect(capabilities.chromeBrowser.capabilities).toEqual({ 'bstack:options': { local: true } }) }) it('should add the "browserstack.local" property to an array of capabilities', async () => { @@ -62,9 +69,9 @@ describe('onPrepare', () => { await service.onPrepare(config, capabilities) expect(capabilities).toEqual([ - { 'browserstack.local': true }, - { 'browserstack.local': true }, - { 'browserstack.local': true } + { 'bstack:options': { local: true } }, + { 'bstack:options': { local: true } }, + { 'bstack:options': { local: true } } ]) }) @@ -105,7 +112,7 @@ describe('onPrepare', () => { describe('onComplete', () => { it('should do nothing if browserstack local is turned on, but not running', () => { - const service = new BrowserstackLauncher({}, [{}], {}) + const service = new BrowserstackLauncher({}, [{}] as any, {} as any) service.browserstackLocal = new Browserstack.Local() const BrowserstackLocalIsRunningSpy = jest.spyOn(service.browserstackLocal, 'isRunning') BrowserstackLocalIsRunningSpy.mockImplementationOnce(() => false) @@ -114,18 +121,18 @@ describe('onComplete', () => { }) it('should kill the process if forcedStop is true', () => { - const service = new BrowserstackLauncher({ forcedStop: true }, [{}], {}) + const service = new BrowserstackLauncher({ forcedStop: true }, [{}] as any, {} as any) service.browserstackLocal = new Browserstack.Local() service.browserstackLocal.pid = 102 - const killSpy = jest.spyOn(process, 'kill').mockImplementationOnce((pid) => pid) + const killSpy = jest.spyOn(process, 'kill').mockImplementationOnce((pid) => pid as any) expect(service.onComplete()).toEqual(102) expect(killSpy).toHaveBeenCalled() expect(service.browserstackLocal.stop).not.toHaveBeenCalled() }) it('should reject with an error, if local.stop throws an error', () => { - const service = new BrowserstackLauncher({}, [{}], {}) + const service = new BrowserstackLauncher({}, [{ browserName: '' }] as any, {} as any) service.browserstackLocal = new Browserstack.Local() const BrowserstackLocalStopSpy = jest.spyOn(service.browserstackLocal, 'stop') BrowserstackLocalStopSpy.mockImplementationOnce((cb) => cb(error)) @@ -134,7 +141,7 @@ describe('onComplete', () => { }) it('should properly resolve if everything works', () => { - const service = new BrowserstackLauncher({}, [{}], {}) + const service = new BrowserstackLauncher({}, [{}] as any, {} as any) service.browserstackLocal = new Browserstack.Local() return expect(service.onComplete()).resolves.toBe(undefined) .then(() => expect(service.browserstackLocal?.stop).toHaveBeenCalled()) diff --git a/packages/wdio-browserstack-service/tests/service.test.ts b/packages/wdio-browserstack-service/tests/service.test.ts index 66f4a28f246..04bfa28fa67 100644 --- a/packages/wdio-browserstack-service/tests/service.test.ts +++ b/packages/wdio-browserstack-service/tests/service.test.ts @@ -1,12 +1,22 @@ -import BrowserstackService from '../src/service' -import got from 'got' +import gotMock from 'got' import logger from '@wdio/logger' +import type { Browser } from 'webdriverio' + +import BrowserstackService from '../src/service' + +interface GotMock extends jest.Mock { + put: jest.Mock +} + +const got = gotMock as unknown as GotMock +const expect = global.expect as unknown as jest.Expect const log = logger('test') -let service, browser +let service: BrowserstackService +let browser: Browser beforeEach(() => { - log.info.mockClear() + (log.info as jest.Mock).mockClear() got.mockClear() got.put.mockClear() got.mockReturnValue(Promise.resolve({ @@ -39,30 +49,31 @@ beforeEach(() => { } } }, browserB: {} - } - service = new BrowserstackService({}, [], { user: 'foo', key: 'bar' }) + } as any as Browser + service = new BrowserstackService({}, [] as any, { user: 'foo', key: 'bar' } as any) }) it('should initialize correctly', () => { - service = new BrowserstackService({}, [], {}) - expect(service._failReasons).toEqual([]) + service = new BrowserstackService({}, [] as any, {} as any) + expect(service['_failReasons']).toEqual([]) }) describe('onReload()', () => { it('should update and get session', async () => { const updateSpy = jest.spyOn(service, '_update') - service._browser = browser - await service.onReload(1, 2) + service['_browser'] = browser + await service.onReload('1', '2') expect(updateSpy).toHaveBeenCalled() expect(got.put).toHaveBeenCalled() expect(got).toHaveBeenCalled() }) it('should update and get multiremote session', async () => { + // @ts-expect-error browser.isMultiremote = true - service._browser = browser + service['_browser'] = browser const updateSpy = jest.spyOn(service, '_update') - await service.onReload(1, 2) + await service.onReload('1', '2') expect(updateSpy).toHaveBeenCalled() expect(got.put).toHaveBeenCalled() expect(got).toHaveBeenCalled() @@ -70,39 +81,39 @@ describe('onReload()', () => { it('should reset failures', async () => { const updateSpy = jest.spyOn(service, '_update') - service._browser = browser + service['_browser'] = browser - service._failReasons = ['Custom Error: Button should be enabled', 'Expected something'] - await service.onReload(1, 2) - expect(updateSpy).toHaveBeenCalledWith(1, { + service['_failReasons'] = ['Custom Error: Button should be enabled', 'Expected something'] + await service.onReload('1', '2') + expect(updateSpy).toHaveBeenCalledWith('1', { status: 'failed', reason: 'Custom Error: Button should be enabled' + '\n' + 'Expected something' }) - expect(service._failReasons).toEqual([]) + expect(service['_failReasons']).toEqual([]) }) }) describe('beforeSession', () => { it('should set some default to make missing user and key parameter apparent', () => { - service.beforeSession({}) - expect(service._config).toEqual({ user: 'NotSetUser', key: 'NotSetKey' }) + service.beforeSession({} as any) + expect(service['_config']).toEqual({ user: 'NotSetUser', key: 'NotSetKey' }) }) it('should set username default to make missing user parameter apparent', () => { - service.beforeSession({ user: 'foo' }) - expect(service._config).toEqual({ user: 'foo', key: 'NotSetKey' }) + service.beforeSession({ user: 'foo' } as any) + expect(service['_config']).toEqual({ user: 'foo', key: 'NotSetKey' }) }) it('should set key default to make missing key parameter apparent', () => { - service.beforeSession({ key: 'bar' }) - expect(service._config).toEqual({ user: 'NotSetUser', key: 'bar' }) + service.beforeSession({ key: 'bar' } as any) + expect(service['_config']).toEqual({ user: 'NotSetUser', key: 'bar' }) }) }) describe('_printSessionURL', () => { it('should get and log session details', async () => { browser.isMultiremote = false - service._browser = browser + service['_browser'] = browser const logInfoSpy = jest.spyOn(log, 'info').mockImplementation((string) => string) await service._printSessionURL() expect(got).toHaveBeenCalledWith( @@ -115,8 +126,9 @@ describe('_printSessionURL', () => { }) it('should get and log multi remote session details', async () => { + // @ts-expect-error browser.isMultiremote = true - service._browser = browser + service['_browser'] = browser const logInfoSpy = jest.spyOn(log, 'info').mockImplementation((string) => string) await service._printSessionURL() expect(got).toHaveBeenCalledWith( @@ -157,7 +169,7 @@ describe('_printSessionURL Appium', () => { }) it('should get and log session details', async () => { - service._browser = browser + service['_browser'] = browser await service._printSessionURL() expect(log.info).toHaveBeenCalled() expect(log.info).toHaveBeenCalledWith( @@ -168,67 +180,72 @@ describe('_printSessionURL Appium', () => { describe('before', () => { it('should set auth to default values if not provided', async () => { - let service = new BrowserstackService({}, [{}], { capabilities: {} }, browser) + let service = new BrowserstackService({}, [{}] as any, { capabilities: {} }) - await service.beforeSession({}) - await service.before(service._config, [], browser) + await service.beforeSession({} as any as any) + await service.before(service['_config'], [], browser as Browser) - expect(service._failReasons).toEqual([]) - expect(service._config.user).toEqual('NotSetUser') - expect(service._config.key).toEqual('NotSetKey') + expect(service['_failReasons']).toEqual([]) + expect(service['_config'].user).toEqual('NotSetUser') + expect(service['_config'].key).toEqual('NotSetKey') - service = new BrowserstackService({}, [{}], { capabilities: {} }, browser) - service.beforeSession({ user: 'blah' }) - await service.before(service._config, [], browser) + service = new BrowserstackService({}, [{}] as any, { capabilities: {} }) + service.beforeSession({ user: 'blah' } as any as any) + await service.before(service['_config'], [], browser) - expect(service._failReasons).toEqual([]) + expect(service['_failReasons']).toEqual([]) - expect(service._config.user).toEqual('blah') - expect(service._config.key).toEqual('NotSetKey') - service = new BrowserstackService({}, [{}], { capabilities: {} }, browser) - service.beforeSession({ key: 'blah' }) - await service.before(service._config, [], browser) + expect(service['_config'].user).toEqual('blah') + expect(service['_config'].key).toEqual('NotSetKey') + service = new BrowserstackService({}, [{}] as any, { capabilities: {} }) + service.beforeSession({ key: 'blah' } as any as any) + await service.before(service['_config'], [], browser) - expect(service._failReasons).toEqual([]) - expect(service._config.user).toEqual('NotSetUser') - expect(service._config.key).toEqual('blah') + expect(service['_failReasons']).toEqual([]) + expect(service['_config'].user).toEqual('NotSetUser') + expect(service['_config'].key).toEqual('blah') }) it('should initialize correctly', () => { - const service = new BrowserstackService({}, [{}], { + const service = new BrowserstackService({}, [{}] as any, { user: 'foo', key: 'bar', capabilities: {} - }, browser) - service.before(service._config, [], browser) + }) + service.before(service['_config'], [], browser) - expect(service._failReasons).toEqual([]) - expect(service._sessionBaseUrl).toEqual('https://api.browserstack.com/automate/sessions') + expect(service['_failReasons']).toEqual([]) + expect(service['_sessionBaseUrl']).toEqual('https://api.browserstack.com/automate/sessions') }) it('should initialize correctly for multiremote', () => { - const service = new BrowserstackService(undefined, [{}], { - user: 'foo', - key: 'bar', - capabilities: {} - }, browser) - delete browser.capabilities - service.before(service._config, [], browser) + const service = new BrowserstackService( + {}, + [{}] as any, + { + user: 'foo', + key: 'bar', + capabilities: [{}] + } + ) + service.before(service['_config'], [], browser) - expect(service._failReasons).toEqual([]) - expect(service._sessionBaseUrl).toEqual('https://api.browserstack.com/automate/sessions') + expect(service['_failReasons']).toEqual([]) + expect(service['_sessionBaseUrl']).toEqual('https://api.browserstack.com/automate/sessions') }) it('should initialize correctly for appium', () => { - const service = new BrowserstackService({}, [{ - app: 'test-app' - }], { - user: 'foo', - key: 'bar', - capabilities: { - app: 'test-app' + const service = new BrowserstackService( + {}, + [{ app: 'test-app' }] as any, + { + user: 'foo', + key: 'bar', + capabilities: { + app: 'test-app' + } as any } - }, browser) + ) browser.capabilities = { app: 'test-app', device: 'iPhone XS', @@ -236,10 +253,10 @@ describe('before', () => { os_version: '12.1', browserName: '', } - service.before(service._config, [], browser) + service.before(service['_config'], [], browser) - expect(service._failReasons).toEqual([]) - expect(service._sessionBaseUrl).toEqual('https://api-cloud.browserstack.com/app-automate/sessions') + expect(service['_failReasons']).toEqual([]) + expect(service['_sessionBaseUrl']).toEqual('https://api-cloud.browserstack.com/app-automate/sessions') }) it('should initialize correctly for appium without global browser capabilities', () => { @@ -249,19 +266,19 @@ describe('before', () => { user: 'foo', key: 'bar', capabilities: { - app: 'test-app' + app: 'test-app' as any } - }, browser) - service.before(service._config, [], browser) + }) + service.before(service['_config'], [], browser) - expect(service._failReasons).toEqual([]) - expect(service._sessionBaseUrl).toEqual('https://api-cloud.browserstack.com/app-automate/sessions') + expect(service['_failReasons']).toEqual([]) + expect(service['_sessionBaseUrl']).toEqual('https://api-cloud.browserstack.com/app-automate/sessions') }) it('should log the url', async () => { - const service = new BrowserstackService({}, [{}], { capabilities: {} }, browser) + const service = new BrowserstackService({}, [{}] as any, { capabilities: {} }) - await service.before(service._config, [], browser) + await service.before(service['_config'], [], browser) expect(log.info).toHaveBeenCalled() expect(log.info).toHaveBeenCalledWith( 'OS X Sierra chrome session: https://www.browserstack.com/automate/builds/1/sessions/2') @@ -270,101 +287,100 @@ describe('before', () => { describe('afterTest', () => { it('should increment failure reasons on fails', () => { - service.before(service._config, [], browser) - service._fullTitle = '' - service.beforeSuite({ title: 'foo', }) + service.before(service['_config'], [], browser) + service['_fullTitle'] = '' + service.beforeSuite({ title: 'foo' } as any) service.afterTest( - { title: 'foo', parent: 'bar' }, - undefined, - { error: { message: 'cool reason' }, result: 1, duration: 5, passed: false, undefined }) - expect(service._failReasons).toContain('cool reason') + { title: 'foo', parent: 'bar' } as any, + undefined as never, + { error: { message: 'cool reason' }, result: 1, duration: 5, passed: false } as any) + expect(service['_failReasons']).toContain('cool reason') service.afterTest( - { title: 'foo2', parent: 'bar2' }, - undefined, - { error: { message: 'not so cool reason' }, result: 1, duration: 7, passed: false, undefined }) + { title: 'foo2', parent: 'bar2' } as any, + undefined as never, + { error: { message: 'not so cool reason' }, result: 1, duration: 7, passed: false } as any) - expect(service._failReasons).toHaveLength(2) - expect(service._failReasons).toContain('cool reason') - expect(service._failReasons).toContain('not so cool reason') + expect(service['_failReasons']).toHaveLength(2) + expect(service['_failReasons']).toContain('cool reason') + expect(service['_failReasons']).toContain('not so cool reason') service.afterTest( - { title: 'foo3', parent: 'bar3' }, - undefined, - { error: undefined, result: 1, duration: 7, passed: false, undefined }) - - expect(service._fullTitle).toBe('bar3 - foo3') - expect(service._failReasons).toHaveLength(3) - expect(service._failReasons).toContain('cool reason') - expect(service._failReasons).toContain('not so cool reason') - expect(service._failReasons).toContain('Unknown Error') + { title: 'foo3', parent: 'bar3' } as any, + undefined as never, + { error: undefined, result: 1, duration: 7, passed: false } as any) + + expect(service['_fullTitle']).toBe('bar3 - foo3') + expect(service['_failReasons']).toHaveLength(3) + expect(service['_failReasons']).toContain('cool reason') + expect(service['_failReasons']).toContain('not so cool reason') + expect(service['_failReasons']).toContain('Unknown Error') }) it('should not increment failure reasons on passes', () => { - service.before(service._config, [], browser) - service.beforeSuite({ title: 'foo', }) + service.before(service['_config'], [], browser) + service.beforeSuite({ title: 'foo' } as any) service.afterTest( - { title: 'foo', parent: 'bar' }, - undefined, - { error: { message: 'cool reason' }, result: 1, duration: 5, passed: true, undefined }) - expect(service._failReasons).toEqual([]) + { title: 'foo', parent: 'bar' } as any, + undefined as never, + { error: { message: 'cool reason' }, result: 1, duration: 5, passed: true } as any) + expect(service['_failReasons']).toEqual([]) service.afterTest( - { title: 'foo2', parent: 'bar2' }, - undefined, - { error: { message: 'not so cool reason' }, result: 1, duration: 5, passed: true, undefined }) + { title: 'foo2', parent: 'bar2' } as any, + undefined as never, + { error: { message: 'not so cool reason' }, result: 1, duration: 5, passed: true } as any) - expect(service._fullTitle).toBe('bar2 - foo2') - expect(service._failReasons).toEqual([]) + expect(service['_fullTitle']).toBe('bar2 - foo2') + expect(service['_failReasons']).toEqual([]) }) it('should set title for Mocha tests', () => { - service.before(service._config, [], browser) - service.beforeSuite({ title: 'foo', }) - service.afterTest({ title: 'bar', parent: 'foo' }, undefined, {}) - expect(service._fullTitle).toBe('foo - bar') + service.before(service['_config'], [], browser) + service.beforeSuite({ title: 'foo' } as any) + service.afterTest({ title: 'bar', parent: 'foo' } as any, undefined as never, {} as any) + expect(service['_fullTitle']).toBe('foo - bar') }) }) describe('afterScenario', () => { it('should increment failure reasons on non-passing statuses (strict mode off)', () => { - const uri = '/some/uri' - service = new BrowserstackService({}, [], - { user: 'foo', key: 'bar', cucumberOpts: { strict: false } }) + service = new BrowserstackService({}, [] as any, + { user: 'foo', key: 'bar', cucumberOpts: { strict: false } } as any) - expect(service._failReasons).toEqual([]) + expect(service['_failReasons']).toEqual([]) - service.afterScenario(uri, {}, {}, { status: 'passed' }) - expect(service._failReasons).toEqual([]) + service.afterScenario({ pickle: {}, result: { status: 2 } }) + expect(service['_failReasons']).toEqual([]) - service.afterScenario(uri, {}, {}, { exception: 'I am Error, most likely', status: 'failed' }) - expect(service._failReasons).toEqual(['I am Error, most likely']) + service.afterScenario({ pickle: {}, result: { status: 6, message: 'I am Error, most likely' } }) + expect(service['_failReasons']).toEqual(['I am Error, most likely']) - service.afterScenario(uri, {}, {}, { status: 'passed' }) - expect(service._failReasons).toEqual(['I am Error, most likely']) + service.afterScenario({ pickle: {}, result: { status: 2 } }) + expect(service['_failReasons']).toEqual(['I am Error, most likely']) - service.afterScenario(uri, {}, {}, { exception: 'I too am Error', status: 'failed' }) - expect(service._failReasons).toEqual(['I am Error, most likely', 'I too am Error']) + service.afterScenario({ pickle: {}, result: { status: 6, message: 'I too am Error' } }) + expect(service['_failReasons']).toEqual(['I am Error, most likely', 'I too am Error']) - service.afterScenario(uri, {}, {}, { exception: 'Step XYZ is undefined', status: 'undefined' }) - expect(service._failReasons).toEqual(['I am Error, most likely', 'I too am Error', 'Step XYZ is undefined']) + service.afterScenario({ pickle: {}, result: { status: 4, message: 'Step XYZ is undefined' } }) + expect(service['_failReasons']).toEqual(['I am Error, most likely', 'I too am Error', 'Step XYZ is undefined']) - service.afterScenario(uri, {}, {}, { exception: 'Step XYZ2 is ambiguous', status: 'ambiguous' }) - expect(service._failReasons).toEqual( + service.afterScenario({ pickle: {}, result: { status: 5, message: 'Step XYZ2 is ambiguous' } }) + expect(service['_failReasons']).toEqual( ['I am Error, most likely', 'I too am Error', 'Step XYZ is undefined', 'Step XYZ2 is ambiguous']) - service.afterScenario(uri, {}, { name: 'Can do something' }, { status: 'pending' }) - expect(service._failReasons).toEqual( + service.afterScenario({ pickle: { name: 'Can do something' }, result: { status: 3 } }) + expect(service['_failReasons']).toEqual( ['I am Error, most likely', 'I too am Error', 'Step XYZ is undefined', 'Step XYZ2 is ambiguous']) - service.afterScenario(uri, {}, {}, { status: 'passed' }) - expect(service._failReasons).toEqual([ + service.afterScenario({ pickle: {}, result: { status: 2 } }) + expect(service['_failReasons']).toEqual([ 'I am Error, most likely', 'I too am Error', 'Step XYZ is undefined', @@ -372,44 +388,43 @@ describe('afterScenario', () => { }) it('should increment failure reasons on non-passing statuses (strict mode on)', () => { - const uri = '/some/uri' - service = new BrowserstackService({}, [], - { user: 'foo', key: 'bar', cucumberOpts: { strict: true } }) + service = new BrowserstackService({}, [] as any, + { user: 'foo', key: 'bar', cucumberOpts: { strict: true }, capabilities: {} }) - expect(service._failReasons).toEqual([]) + expect(service['_failReasons']).toEqual([]) - service.afterScenario(uri, {}, {}, { status: 'passed' }) - expect(service._failReasons).toEqual([]) + service.afterScenario({ pickle: {}, result: { status: 2 } }) + expect(service['_failReasons']).toEqual([]) - service.afterScenario(uri, {}, {}, { exception: 'I am Error, most likely', status: 'failed' }) - expect(service._failReasons).toEqual(['I am Error, most likely']) + service.afterScenario({ pickle: {}, result: { message: 'I am Error, most likely', status: 6 } }) + expect(service['_failReasons']).toEqual(['I am Error, most likely']) - service.afterScenario(uri, {}, {}, { status: 'passed' }) - expect(service._failReasons).toEqual(['I am Error, most likely']) + service.afterScenario({ pickle: {}, result: { status: 2 } }) + expect(service['_failReasons']).toEqual(['I am Error, most likely']) - service.afterScenario(uri, {}, {}, { exception: 'I too am Error', status: 'failed' }) - expect(service._failReasons).toEqual(['I am Error, most likely', 'I too am Error']) + service.afterScenario({ pickle: {}, result: { status: 6, message: 'I too am Error' } }) + expect(service['_failReasons']).toEqual(['I am Error, most likely', 'I too am Error']) - service.afterScenario(uri, {}, {}, { exception: 'Step XYZ is undefined', status: 'undefined' }) - expect(service._failReasons).toEqual(['I am Error, most likely', 'I too am Error', 'Step XYZ is undefined']) + service.afterScenario({ pickle: {}, result: { status: 4, message: 'Step XYZ is undefined' } }) + expect(service['_failReasons']).toEqual(['I am Error, most likely', 'I too am Error', 'Step XYZ is undefined']) - service.afterScenario(uri, {}, {}, { exception: 'Step XYZ2 is ambiguous', status: 'ambiguous' }) - expect(service._failReasons).toEqual( + service.afterScenario({ pickle: {}, result: { status: 5, message: 'Step XYZ2 is ambiguous' } }) + expect(service['_failReasons']).toEqual( ['I am Error, most likely', 'I too am Error', 'Step XYZ is undefined', 'Step XYZ2 is ambiguous']) - service.afterScenario(uri, {}, { name: 'Can do something' }, { status: 'pending' }) - expect(service._failReasons).toEqual( + service.afterScenario({ pickle: { name: 'Can do something' }, result: { status: 3 } }) + expect(service['_failReasons']).toEqual( ['I am Error, most likely', 'I too am Error', 'Step XYZ is undefined', 'Step XYZ2 is ambiguous', 'Some steps/hooks are pending for scenario "Can do something"']) - service.afterScenario(uri, {}, {}, { status: 'passed' }) - expect(service._failReasons).toEqual([ + service.afterScenario({ pickle: {}, result: { status: 2 } }) + expect(service['_failReasons']).toEqual([ 'I am Error, most likely', 'I too am Error', 'Step XYZ is undefined', @@ -421,14 +436,14 @@ describe('afterScenario', () => { describe('after', () => { it('should call _update when session has no errors (exit code 0)', async () => { const updateSpy = jest.spyOn(service, '_update') - await service.before(service._config, [], browser) + await service.before(service['_config'], [], browser) - service._failReasons = [] - service._fullTitle = 'foo - bar' + service['_failReasons'] = [] + service['_fullTitle'] = 'foo - bar' await service.after(0) - expect(updateSpy).toHaveBeenCalledWith(service._browser.sessionId, + expect(updateSpy).toHaveBeenCalledWith(service['_browser']?.sessionId, { status: 'passed', name: 'foo - bar', @@ -445,13 +460,13 @@ describe('after', () => { it('should call _update when session has errors (exit code 1)', async () => { const updateSpy = jest.spyOn(service, '_update') - await service.before(service._config, [], browser) + await service.before(service['_config'], [], browser) - service._fullTitle = 'foo - bar' - service._failReasons = ['I am failure'] + service['_fullTitle'] = 'foo - bar' + service['_failReasons'] = ['I am failure'] await service.after(1) - expect(updateSpy).toHaveBeenCalledWith(service._browser.sessionId, + expect(updateSpy).toHaveBeenCalledWith(service['_browser']?.sessionId, { status: 'failed', name: 'foo - bar', @@ -468,24 +483,21 @@ describe('after', () => { describe('Cucumber only', function () { it('should call _update with status "failed" if strict mode is "on" and all tests are pending', async () => { - service = new BrowserstackService({}, [], - { user: 'foo', key: 'bar', cucumberOpts: { strict: true } }) + service = new BrowserstackService({}, [] as any, + { user: 'foo', key: 'bar', cucumberOpts: { strict: true } } as any) const updateSpy = jest.spyOn(service, '_update') - await service.before(service._config, [], browser) - await service.beforeFeature({}, { document: { feature: { name: 'Feature1' } } }) + await service.before(service['_config'], [], browser) + await service.beforeFeature(null, { name: 'Feature1' }) - await service.afterScenario({}, {}, - { name: 'Can do something but pending 1' }, { status: 'pending' }) - await service.afterScenario({}, {}, - { name: 'Can do something but pending 2' }, { status: 'pending' }) - await service.afterScenario({}, {}, - { name: 'Can do something but pending 3' }, { status: 'pending' }) + await service.afterScenario({ pickle: { name: 'Can do something but pending 1' }, result: { status: 3 } }) + await service.afterScenario({ pickle: { name: 'Can do something but pending 2' }, result: { status: 3 } }) + await service.afterScenario({ pickle: { name: 'Can do something but pending 3' }, result: { status: 3 } }) await service.after(1) - expect(updateSpy).toHaveBeenLastCalledWith(service._browser.sessionId, { + expect(updateSpy).toHaveBeenLastCalledWith(service['_browser']?.sessionId, { name: 'Feature1', reason: 'Some steps/hooks are pending for scenario "Can do something but pending 1"' + '\n' + 'Some steps/hooks are pending for scenario "Can do something but pending 2"' + '\n' + @@ -496,25 +508,22 @@ describe('after', () => { }) it('should call _update with status "passed" when strict mode is "off" and only passed and pending tests ran', async () => { - service = new BrowserstackService({}, [], - { user: 'foo', key: 'bar', cucumberOpts: { strict: false } }) + service = new BrowserstackService({}, [] as any, + { user: 'foo', key: 'bar', cucumberOpts: { strict: false } } as any) const updateSpy = jest.spyOn(service, '_update') - await service.before(service._config, [], browser) - await service.beforeFeature({}, { document: { feature: { name: 'Feature1' } } }) + await service.before(service['_config'], [], browser) + await service.beforeFeature(null, { name: 'Feature1' }) - await service.afterScenario({}, {}, - { name: 'Can do something' }, { status: 'passed' }) - await service.afterScenario({}, {}, - { name: 'Can do something' }, { status: 'pending' }) - await service.afterScenario({}, {}, - { name: 'Can do something' }, { status: 'passed' }) + await service.afterScenario({ pickle: { name: 'Can do something' }, result: { status: 1 } }) + await service.afterScenario({ pickle: { name: 'Can do something' }, result: { status: 3 } }) + await service.afterScenario({ pickle: { name: 'Can do something' }, result: { status: 1 } }) await service.after(0) expect(updateSpy).toHaveBeenCalled() - expect(updateSpy).toHaveBeenLastCalledWith(service._browser.sessionId, { + expect(updateSpy).toHaveBeenLastCalledWith(service['_browser']?.sessionId, { name: 'Feature1', reason: undefined, status: 'passed', @@ -522,25 +531,22 @@ describe('after', () => { }) it('should call _update with status is "failed" when strict mode is "on" and only passed and pending tests ran', async () => { - service = new BrowserstackService({}, [], - { user: 'foo', key: 'bar', cucumberOpts: { strict: true } }) + service = new BrowserstackService({}, [] as any, + { user: 'foo', key: 'bar', cucumberOpts: { strict: true } } as any) const updateSpy = jest.spyOn(service, '_update') - await service.before(service._config, [], browser) - await service.beforeFeature({}, { document: { feature: { name: 'Feature1' } } }) + await service.before(service['_config'], [], browser) + await service.beforeFeature(null, { name: 'Feature1' }) - await service.afterScenario({}, {}, - { name: 'Can do something 1' }, { status: 'passed' }) - await service.afterScenario({}, {}, - { name: 'Can do something but pending' }, { status: 'pending' }) - await service.afterScenario({}, {}, - { name: 'Can do something 2' }, { status: 'passed' }) + await service.afterScenario({ pickle: { name: 'Can do something 1' }, result: { status: 1 } }) + await service.afterScenario({ pickle: { name: 'Can do something but pending' }, result: { status: 3 } }) + await service.afterScenario({ pickle: { name: 'Can do something 2' }, result: { status: 1 } }) await service.after(1) expect(updateSpy).toHaveBeenCalled() - expect(updateSpy).toHaveBeenCalledWith(service._browser.sessionId, { + expect(updateSpy).toHaveBeenCalledWith(service['_browser']?.sessionId, { name: 'Feature1', reason: 'Some steps/hooks are pending for scenario "Can do something but pending"', status: 'failed', @@ -550,21 +556,16 @@ describe('after', () => { it('should call _update with status "passed" when all tests are skipped', async () => { const updateSpy = jest.spyOn(service, '_update') - service.strict = true - - await service.before(service._config, [], browser) - await service.beforeFeature({}, { document: { feature: { name: 'Feature1' } } }) + await service.before(service['_config'], [], browser) + await service.beforeFeature(null, { name: 'Feature1' }) - await service.afterScenario({}, {}, - { name: 'Can do something skipped 1' }, { status: 'skipped' }) - await service.afterScenario({}, {}, - { name: 'Can do something skipped 2' }, { status: 'skipped' }) - await service.afterScenario({}, {}, - { name: 'Can do something skipped 3' }, { status: 'skipped' }) + await service.afterScenario({ pickle: { name: 'Can do something skipped 1' }, result: { status: 2 } }) + await service.afterScenario({ pickle: { name: 'Can do something skipped 2' }, result: { status: 2 } }) + await service.afterScenario({ pickle: { name: 'Can do something skipped 3' }, result: { status: 2 } }) await service.after(0) - expect(updateSpy).toHaveBeenCalledWith(service._browser.sessionId, { + expect(updateSpy).toHaveBeenCalledWith(service['_browser']?.sessionId, { name: 'Feature1', reason: undefined, status: 'passed', @@ -572,32 +573,29 @@ describe('after', () => { }) it('should call _update with status "failed" when strict mode is "on" and only failed and pending tests ran', async () => { - service = new BrowserstackService({}, [], - { user: 'foo', key: 'bar', cucumberOpts: { strict: true } }) + service = new BrowserstackService({}, [] as any, + { user: 'foo', key: 'bar', cucumberOpts: { strict: true } } as any) const updateSpy = jest.spyOn(service, '_update') const afterSpy = jest.spyOn(service, 'after') - await service.beforeSession(service._config) - await service.before(service._config, [], browser) - await service.beforeFeature({}, { document: { feature: { name: 'Feature1' } } }) + await service.beforeSession(service['_config'] as any) + await service.before(service['_config'], [], browser) + await service.beforeFeature(null, { name: 'Feature1' }) - expect(updateSpy).toHaveBeenCalledWith(service._browser.sessionId, { + expect(updateSpy).toHaveBeenCalledWith(service['_browser']?.sessionId, { name: 'Feature1' }) - await service.afterScenario({}, {}, - { name: 'Can do something failed 1' }, { exception: 'I am error, hear me roar', status: 'failed' }) - await service.afterScenario({}, {}, - { name: 'Can do something but pending 2' }, { status: 'pending' }) - await service.afterScenario({}, {}, - { name: 'Can do something but passed 3' }, { status: 'passed' }) + await service.afterScenario({ pickle: { name: 'Can do something failed 1' }, result: { message: 'I am error, hear me roar', status: 6 } }) + await service.afterScenario({ pickle: { name: 'Can do something but pending 2' }, result: { status: 3 } }) + await service.afterScenario({ pickle: { name: 'Can do something but passed 3' }, result: { status: 2 } }) await service.after(1) expect(updateSpy).toHaveBeenCalledTimes(2) expect(updateSpy).toHaveBeenLastCalledWith( - service._browser.sessionId, { + service['_browser']?.sessionId, { name: 'Feature1', reason: 'I am error, hear me roar' + @@ -611,28 +609,23 @@ describe('after', () => { it('should call _update with status "failed" when strict mode is "off" and only failed and pending tests ran', async () => { const updateSpy = jest.spyOn(service, '_update') - service.strict = false + await service.beforeSession(service['_config'] as any) + await service.before(service['_config'], [], browser) + await service.beforeFeature(null, { name: 'Feature1' }) - await service.beforeSession(service._config) - await service.before(service._config, [], browser) - await service.beforeFeature({}, { document: { feature: { name: 'Feature1' } } }) - - expect(updateSpy).toHaveBeenCalledWith(service._browser.sessionId, { + expect(updateSpy).toHaveBeenCalledWith(service['_browser']?.sessionId, { name: 'Feature1' }) - await service.afterScenario({}, {}, - { name: 'Can do something failed 1' }, { exception: 'I am error, hear me roar', status: 'failed' }) - await service.afterScenario({}, {}, - { name: 'Can do something but pending 2' }, { status: 'pending' }) - await service.afterScenario({}, {}, - { name: 'Can do something but passed 3' }, { status: 'passed' }) + await service.afterScenario({ pickle: { name: 'Can do something failed 1' }, result: { message: 'I am error, hear me roar', status: 6 } }) + await service.afterScenario({ pickle: { name: 'Can do something but pending 2' }, result: { status: 3 } }) + await service.afterScenario({ pickle: { name: 'Can do something but passed 3' }, result: { status: 2 } }) await service.after(1) expect(updateSpy).toHaveBeenCalledTimes(2) expect(updateSpy).toHaveBeenLastCalledWith( - service._browser.sessionId, { + service['_browser']?.sessionId, { name: 'Feature1', reason: 'I am error, hear me roar', status: 'failed', @@ -642,44 +635,48 @@ describe('after', () => { describe('preferScenarioName', () => { describe('enabled', () => { - ['failed', 'ambiguous', 'undefined', 'unknown'].map(status => + [ + { status: 6, body: { + name: 'Feature1', + reason: 'Unknown Error', + status: 'failed', + } }, + { status: 2, body: { + name: 'Can do something single', + reason: undefined, + status: 'failed', + } } + /*, 5, 4, 0*/ + ].map(({ status, body }) => it(`should call _update /w status failed and name of Scenario when single "${status}" Scenario ran`, async () => { - service = new BrowserstackService({ preferScenarioName : true }, [], - { user: 'foo', key: 'bar', cucumberOpts: { strict: false } }) + service = new BrowserstackService({ preferScenarioName : true }, [] as any, + { user: 'foo', key: 'bar', cucumberOpts: { strict: false } } as any) service.before({}, [], browser) const updateSpy = jest.spyOn(service, '_update') - await service.beforeFeature({}, { document: { feature: { name: 'Feature1' } } }) - - await service.afterScenario({}, {}, - { name: 'Can do something single' }, { status }) - + await service.beforeFeature(null, { name: 'Feature1' }) + await service.afterScenario({ pickle: { name: 'Can do something single' }, result: { status } }) await service.after(1) - expect(updateSpy).toHaveBeenLastCalledWith(service._browser.sessionId, { - name: 'Can do something single', - reason: 'Unknown Error', - status: 'failed', - }) + expect(updateSpy).toHaveBeenLastCalledWith(service['_browser']?.sessionId, body) }) ) it('should call _update /w status passed and name of Scenario when single "passed" Scenario ran', async () => { - service = new BrowserstackService({ preferScenarioName : true }, [], - { user: 'foo', key: 'bar', cucumberOpts: { strict: false } }) + service = new BrowserstackService({ preferScenarioName : true }, [] as any, + { user: 'foo', key: 'bar', cucumberOpts: { strict: false } } as any) service.before({}, [], browser) const updateSpy = jest.spyOn(service, '_update') - await service.beforeFeature({}, { document: { feature: { name: 'Feature1' } } }) + await service.beforeFeature(null, { name: 'Feature1' }) - await service.afterScenario({}, {}, - { name: 'Can do something single' }, { status: 'passed' }) + await service.afterScenario({ pickle: { name: 'Can do something single' }, result: { status: 2 } }) await service.after(0) - expect(updateSpy).toHaveBeenLastCalledWith(service._browser.sessionId, { + expect(updateSpy).toHaveBeenLastCalledWith(service['_browser']?.sessionId, { name: 'Can do something single', reason: undefined, status: 'passed', @@ -688,22 +685,21 @@ describe('after', () => { }) describe('disabled', () => { - ['failed', 'ambiguous', 'undefined', 'unknown'].map(status => + [6, 5, 4, 0].map(status => it(`should call _update /w status failed and name of Feature when single "${status}" Scenario ran`, async () => { - service = new BrowserstackService({ preferScenarioName : false }, [], - { user: 'foo', key: 'bar', cucumberOpts: { strict: false } }) + service = new BrowserstackService({ preferScenarioName : false }, [] as any, + { user: 'foo', key: 'bar', cucumberOpts: { strict: false } } as any) service.before({}, [], browser) const updateSpy = jest.spyOn(service, '_update') - await service.beforeFeature({}, { document: { feature: { name: 'Feature1' } } }) + await service.beforeFeature(null, { name: 'Feature1' }) - await service.afterScenario({}, {}, - { name: 'Can do something single' }, { status }) + await service.afterScenario({ pickle: { name: 'Can do something single' }, result: { status } }) await service.after(1) - expect(updateSpy).toHaveBeenLastCalledWith(service._browser.sessionId, { + expect(updateSpy).toHaveBeenLastCalledWith(service['_browser']?.sessionId, { name: 'Feature1', reason: 'Unknown Error', status: 'failed', @@ -712,20 +708,21 @@ describe('after', () => { ) it('should call _update /w status passed and name of Feature when single "passed" Scenario ran', async () => { - service = new BrowserstackService({ preferScenarioName : false }, [], - { user: 'foo', key: 'bar', cucumberOpts: { strict: false } }) + service = new BrowserstackService({ preferScenarioName : false }, [] as any, + { user: 'foo', key: 'bar', cucumberOpts: { strict: false } } as any) service.before({}, [], browser) const updateSpy = jest.spyOn(service, '_update') - await service.beforeFeature({}, { document: { feature: { name: 'Feature1' } } }) - - await service.afterScenario({}, {}, - { name: 'Can do something single' }, { status: 'passed' }) + await service.beforeFeature(null, { name: 'Feature1' }) + await service.afterScenario({ + pickle: { name: 'Can do something single' }, + result: { status: 2 } + }) await service.after(0) - expect(updateSpy).toHaveBeenLastCalledWith(service._browser.sessionId, { + expect(updateSpy).toHaveBeenLastCalledWith(service['_browser']?.sessionId, { name: 'Feature1', reason: undefined, status: 'passed', diff --git a/packages/wdio-browserstack-service/tests/util.test.ts b/packages/wdio-browserstack-service/tests/util.test.ts index d4fc0c8613a..0faebc76c0e 100644 --- a/packages/wdio-browserstack-service/tests/util.test.ts +++ b/packages/wdio-browserstack-service/tests/util.test.ts @@ -1,3 +1,5 @@ +import type { Browser, MultiRemoteBrowser } from 'webdriverio' + import { getBrowserDescription, getBrowserCapabilities, isBrowserstackCapability } from '../src/util' describe('getBrowserCapabilities', () => { @@ -6,9 +8,9 @@ describe('getBrowserCapabilities', () => { capabilities: { browser: 'browser' } - } + } as Browser expect(getBrowserCapabilities(browser)) - .toEqual(browser.capabilities) + .toEqual(browser.capabilities as any) }) it('should get multiremote browser capabilities', () => { @@ -19,16 +21,16 @@ describe('getBrowserCapabilities', () => { browser: 'browser' } } - } + } as any as MultiRemoteBrowser expect(getBrowserCapabilities(browser, {}, 'browserA')) - .toEqual(browser.browserA.capabilities) + .toEqual(browser.browserA.capabilities as any) }) it('should handle null multiremote browser capabilities', () => { const browser = { isMultiremote: true, browserA: {} - } + } as any as MultiRemoteBrowser expect(getBrowserCapabilities(browser, {}, 'browserB')).toEqual({}) }) @@ -38,9 +40,9 @@ describe('getBrowserCapabilities', () => { browser: 'browser', os: 'OS X', } - } + } as any as Browser expect(getBrowserCapabilities(browser, { os: 'Windows' })) - .toEqual({ os:'Windows', browser: 'browser' }) + .toEqual({ os:'Windows', browser: 'browser' } as any) }) it('should merge multiremote service capabilities and browser capabilities', () => { @@ -52,16 +54,16 @@ describe('getBrowserCapabilities', () => { os: 'OS X', } } - } + } as any as MultiRemoteBrowser expect(getBrowserCapabilities(browser, { browserA: { capabilities: { os: 'Windows' } } }, 'browserA')) - .toEqual({ os:'Windows', browser: 'browser' }) + .toEqual({ os:'Windows', browser: 'browser' } as any) }) it('should handle null multiremote browser capabilities', () => { const browser = { isMultiremote: true, browserA: {} - } + } as any as MultiRemoteBrowser expect(getBrowserCapabilities(browser, {}, 'browserB')) .toEqual({}) }) @@ -70,8 +72,8 @@ describe('getBrowserCapabilities', () => { const browser = { isMultiremote: true, browserA: {} - } - expect(getBrowserCapabilities(browser, { browserB: {} }, 'browserB')) + } as any as MultiRemoteBrowser + expect(getBrowserCapabilities(browser, { browserB: {} } as any, 'browserB')) .toEqual({}) }) }) @@ -108,7 +110,9 @@ describe('getBrowserDescription', () => { }) it('should not crash when capabilities is null or undefined', () => { + // @ts-expect-error test invalid params expect(getBrowserDescription(undefined)).toEqual('') + // @ts-expect-error test invalid params expect(getBrowserDescription(null)).toEqual('') }) }) @@ -117,6 +121,7 @@ describe('isBrowserstackCapability', () => { it('should detect browserstack W3C capabilities', () => { expect(isBrowserstackCapability({})).toBe(false) expect(isBrowserstackCapability()).toBe(false) + // @ts-expect-error test invalid params expect(isBrowserstackCapability({ 'bstack:options': null })).toBe(false) expect(isBrowserstackCapability({ 'bstack:options': {} })).toBe(true) }) diff --git a/packages/wdio-browserstack-service/tsconfig.json b/packages/wdio-browserstack-service/tsconfig.json index c5cf1bf02e5..d660189bdb9 100644 --- a/packages/wdio-browserstack-service/tsconfig.json +++ b/packages/wdio-browserstack-service/tsconfig.json @@ -3,10 +3,10 @@ "compilerOptions": { "baseUrl": ".", "outDir": "./build", - "rootDir": "./src", - "types": ["webdriverio", "./"] + "rootDir": "./src" }, "include": [ - "src/**/*" + "src/**/*", + "../../@types" ] } diff --git a/packages/wdio-browserstack-service/tsconfig.prod.json b/packages/wdio-browserstack-service/tsconfig.prod.json index 4768dcb4fba..75cf652ead3 100644 --- a/packages/wdio-browserstack-service/tsconfig.prod.json +++ b/packages/wdio-browserstack-service/tsconfig.prod.json @@ -7,6 +7,7 @@ "types": ["webdriverio", "./"] }, "include": [ - "src/**/*" + "src/**/*", + "../../@types" ] } diff --git a/packages/wdio-cli/package.json b/packages/wdio-cli/package.json index c3f7e87c08b..efd413bad86 100644 --- a/packages/wdio-cli/package.json +++ b/packages/wdio-cli/package.json @@ -38,6 +38,7 @@ "@types/recursive-readdir": "^2.2.0", "@wdio/config": "6.12.1", "@wdio/logger": "6.10.10", + "@wdio/types": "^6.10.6", "@wdio/utils": "6.11.0", "async-exit-hook": "^2.0.1", "chalk": "^4.0.0", @@ -57,5 +58,6 @@ }, "publishConfig": { "access": "public" - } + }, + "types": "./build/index.d.ts" } diff --git a/packages/wdio-cli/src/commands/config.ts b/packages/wdio-cli/src/commands/config.ts index d9f7dcf7d30..c19da95d885 100644 --- a/packages/wdio-cli/src/commands/config.ts +++ b/packages/wdio-cli/src/commands/config.ts @@ -116,7 +116,7 @@ const runConfig = async function (useYarn: boolean, yes: boolean, exit = false) const parsedAnswers: ParsedAnswers = { ...answers, - runner: runnerPackage.short, + runner: runnerPackage.short as 'local', framework: frameworkPackage.short, reporters: reporterPackages.map(({ short }) => short), services: servicePackages.map(({ short }) => short), diff --git a/packages/wdio-cli/src/commands/repl.ts b/packages/wdio-cli/src/commands/repl.ts index 301580420f1..3e36d4ba916 100644 --- a/packages/wdio-cli/src/commands/repl.ts +++ b/packages/wdio-cli/src/commands/repl.ts @@ -44,13 +44,23 @@ export const builder = (yargs: yargs.Argv) => { .help() } +declare global { + namespace NodeJS { + interface Global { + $: any + $$: any + browser: any + } + } +} + export const handler = async (argv: ReplCommandArguments) => { const caps = getCapabilities(argv) /** * runner option required to wrap commands within Fibers context */ - const execMode = hasWdioSyncSupport ? { runner: 'repl' } : {} + const execMode = hasWdioSyncSupport ? { runner: 'local' as const } : {} const client = await remote({ ...argv, ...caps, ...execMode }) global.$ = client.$.bind(client) diff --git a/packages/wdio-cli/src/index.ts b/packages/wdio-cli/src/index.ts index 3fadf032347..55ec0b80c3a 100644 --- a/packages/wdio-cli/src/index.ts +++ b/packages/wdio-cli/src/index.ts @@ -81,3 +81,4 @@ export const run = async () => { } export default Launcher +export * from './types' diff --git a/packages/wdio-cli/src/interface.ts b/packages/wdio-cli/src/interface.ts index 95f67827b2c..6e3974fdc25 100644 --- a/packages/wdio-cli/src/interface.ts +++ b/packages/wdio-cli/src/interface.ts @@ -1,7 +1,7 @@ import chalk from 'chalk' import { EventEmitter } from 'events' import logger from '@wdio/logger' -import type { ConfigOptions } from '@wdio/config' +import type { Options, Capabilities } from '@wdio/types' import { getRunnerName } from './utils' @@ -24,7 +24,7 @@ interface CLIInterfaceEvent { } interface Job { - caps: WebDriver.Capabilities | WebDriver.W3CCapabilities | WebdriverIO.MultiRemoteCapabilities + caps: Capabilities.DesiredCapabilities | Capabilities.W3CCapabilities | Capabilities.MultiRemoteCapabilities specs: string[], hasTests: boolean } @@ -54,7 +54,7 @@ export default class WDIOCLInterface extends EventEmitter { } constructor( - private _config: ConfigOptions, + private _config: Options.Testrunner, public totalWorkerCnt: number, private _isWatchMode = false ) { @@ -135,7 +135,7 @@ export default class WDIOCLInterface extends EventEmitter { onJobComplete(cid: string, job?: Job, retries = 0, message = '', _logger: Function = this.log) { const details = [`[${cid}]`, message] if (job) { - details.push('in', getRunnerName(job.caps as WebDriver.DesiredCapabilities), this.getFilenames(job.specs)) + details.push('in', getRunnerName(job.caps as Capabilities.DesiredCapabilities), this.getFilenames(job.specs)) } if (retries > 0) { details.push(`(${retries} retries)`) diff --git a/packages/wdio-cli/src/launcher.ts b/packages/wdio-cli/src/launcher.ts index 220fecd65ca..7e7dd7b8271 100644 --- a/packages/wdio-cli/src/launcher.ts +++ b/packages/wdio-cli/src/launcher.ts @@ -3,8 +3,9 @@ import fs from 'fs-extra' import exitHook from 'async-exit-hook' import logger from '@wdio/logger' -import { ConfigParser, ConfigOptions, Capabilities } from '@wdio/config' +import { ConfigParser } from '@wdio/config' import { initialisePlugin, initialiseLauncherService, sleep } from '@wdio/utils' +import type { Options, Capabilities, Services } from '@wdio/types' import CLInterface from './interface' import { RunCommandArguments } from './types' @@ -14,7 +15,7 @@ const log = logger('@wdio/cli:launcher') interface Schedule { cid: number - caps: Capabilities + caps: Capabilities.Capabilities specs: WorkerSpecs[] availableInstances: number runningInstances: number @@ -36,7 +37,7 @@ interface EndMessage { class Launcher { configParser: ConfigParser isMultiremote: boolean - runner: WebdriverIO.RunnerInstance + runner: Services.RunnerInstance interface: CLInterface private _exitCode = 0 @@ -46,7 +47,7 @@ class Launcher { private _runnerStarted = 0 private _runnerFailed = 0 - private _launcher?: WebdriverIO.ServiceInstance[] + private _launcher?: Services.ServiceInstance[] private _resolve?: Function constructor( @@ -59,7 +60,7 @@ class Launcher { this.configParser.merge(_args) const config = this.configParser.getConfig() - const capabilities = this.configParser.getCapabilities() as (WebDriver.Capabilities | WebDriver.W3CCapabilities | WebdriverIO.MultiRemoteCapabilities) + const capabilities = this.configParser.getCapabilities() as (Capabilities.Capabilities | Capabilities.W3CCapabilities | Capabilities.MultiRemoteCapabilities) this.isMultiremote = !Array.isArray(capabilities) if (config.outputDir) { @@ -71,11 +72,11 @@ class Launcher { const totalWorkerCnt = Array.isArray(capabilities) ? capabilities - .map((c: WebDriver.DesiredCapabilities) => this.configParser.getSpecs(c.specs, c.exclude).length) + .map((c: Capabilities.DesiredCapabilities) => this.configParser.getSpecs(c.specs, c.exclude).length) .reduce((a, b) => a + b, 0) : 1 - const Runner = (initialisePlugin(config.runner!, 'runner') as WebdriverIO.RunnerPlugin).default + const Runner = (initialisePlugin(config.runner!, 'runner') as Services.RunnerPlugin).default this.runner = new Runner(_configFilePath, config) this.interface = new CLInterface(config, totalWorkerCnt, this._isWatchMode) @@ -96,8 +97,8 @@ class Launcher { try { const config = this.configParser.getConfig() - const caps = this.configParser.getCapabilities() as Capabilities - const { ignoredWorkerServices, launcherServices } = initialiseLauncherService(config, caps as WebDriver.DesiredCapabilities) + const caps = this.configParser.getCapabilities() as Capabilities.Capabilities + const { ignoredWorkerServices, launcherServices } = initialiseLauncherService(config, caps as Capabilities.DesiredCapabilities) this._launcher = launcherServices this._args.ignoredWorkerServices = ignoredWorkerServices @@ -148,11 +149,11 @@ class Launcher { /** * run without triggering onPrepare/onComplete hooks */ - runMode (config: Required, caps: Capabilities): Promise { + runMode (config: Required, caps: Capabilities.Capabilities): Promise { /** * fail if no caps were found */ - if (!caps || (!this.isMultiremote && !caps.length)) { + if (!caps) { return new Promise((resolve) => { log.error('Missing capabilities, exiting with failure') return resolve(1) @@ -175,7 +176,7 @@ class Launcher { this._schedule.push({ cid: cid++, caps, - specs: this.configParser.getSpecs((caps as WebDriver.DesiredCapabilities).specs, (caps as WebDriver.DesiredCapabilities).exclude).map(s => ({ files: [s], retries: specFileRetries })), + specs: this.configParser.getSpecs((caps as Capabilities.DesiredCapabilities).specs, (caps as Capabilities.DesiredCapabilities).exclude).map(s => ({ files: [s], retries: specFileRetries })), availableInstances: config.maxInstances || 1, runningInstances: 0 }) @@ -183,12 +184,12 @@ class Launcher { /** * Regular mode */ - for (let capabilities of caps as (WebDriver.DesiredCapabilities | WebDriver.W3CCapabilities)[]) { + for (let capabilities of caps as (Capabilities.DesiredCapabilities | Capabilities.W3CCapabilities)[]) { this._schedule.push({ cid: cid++, - caps: capabilities as Capabilities, - specs: this.configParser.getSpecs((capabilities as WebDriver.DesiredCapabilities).specs, (capabilities as WebDriver.DesiredCapabilities).exclude).map(s => ({ files: [s], retries: specFileRetries })), - availableInstances: (capabilities as WebDriver.DesiredCapabilities).maxInstances || config.maxInstancesPerCapability, + caps: capabilities as Capabilities.Capabilities, + specs: this.configParser.getSpecs((capabilities as Capabilities.DesiredCapabilities).specs, (capabilities as Capabilities.DesiredCapabilities).exclude).map(s => ({ files: [s], retries: specFileRetries })), + availableInstances: (capabilities as Capabilities.DesiredCapabilities).maxInstances || config.maxInstancesPerCapability, runningInstances: 0 }) } @@ -273,7 +274,7 @@ class Launcher { let specs = schedulableCaps[0].specs.shift() as NonNullable this.startInstance( specs.files, - schedulableCaps[0].caps as WebDriver.DesiredCapabilities, + schedulableCaps[0].caps as Capabilities.DesiredCapabilities, schedulableCaps[0].cid, specs.rid, specs.retries @@ -310,7 +311,7 @@ class Launcher { */ async startInstance( specs: string[], - caps: WebDriver.DesiredCapabilities | WebDriver.W3CCapabilities | WebdriverIO.MultiRemoteCapabilities, + caps: Capabilities.DesiredCapabilities | Capabilities.W3CCapabilities | Capabilities.MultiRemoteCapabilities, cid: number, rid: string | undefined, retries: number diff --git a/packages/wdio-cli/src/types.ts b/packages/wdio-cli/src/types.ts index 155b55cc5eb..8dd5939fa66 100644 --- a/packages/wdio-cli/src/types.ts +++ b/packages/wdio-cli/src/types.ts @@ -3,7 +3,7 @@ import { BACKEND_CHOICES, MODE_OPTIONS, REGION_OPTION, COMPILER_OPTION_ANSWERS } type ValueOf = T[keyof T] export interface Questionnair { - runner: string + runner: 'local' backend: ValueOf hostname: string port: string @@ -33,7 +33,7 @@ export interface Questionnair { } export interface ParsedAnswers extends Omit { - runner: string + runner: 'local' framework: string reporters: string[] services: string[] diff --git a/packages/wdio-cli/src/utils.ts b/packages/wdio-cli/src/utils.ts index b49cc9608f5..74c6b2cc596 100644 --- a/packages/wdio-cli/src/utils.ts +++ b/packages/wdio-cli/src/utils.ts @@ -7,7 +7,7 @@ import readDir from 'recursive-readdir' import { SevereServiceError } from 'webdriverio' import { execSync } from 'child_process' import { promisify } from 'util' -import type { ConfigOptions, Capabilities } from '@wdio/config' +import type { Options, Capabilities, Services } from '@wdio/types' import { ReplCommandArguments, Questionnair, SupportedPackage, OnCompleteResult, ParsedAnswers } from './types' import { EXCLUSIVE_SERVICES, ANDROID_CONFIG, IOS_CONFIG, QUESTIONNAIRE } from './constants' @@ -21,12 +21,12 @@ const renderFile = promisify(ejs.renderFile) as (path: string, data: Record { + return Promise.all(launcher.map(async (service: Services.ServiceInstance) => { try { if (typeof service[hookName] === 'function') { await (service[hookName] as Function)(...args) @@ -84,8 +84,8 @@ export async function runLauncherHook(hook: Function | Function[], ...args: any[ */ export async function runOnCompleteHook( onCompleteHook: Function | Function[], - config: ConfigOptions, - capabilities: Capabilities, + config: Options.Testrunner, + capabilities: Capabilities.Capabilities, exitCode: number, results: OnCompleteResult ) { @@ -107,7 +107,7 @@ export async function runOnCompleteHook( /** * get runner identification by caps */ -export function getRunnerName (caps: WebDriver.DesiredCapabilities = {}) { +export function getRunnerName (caps: Capabilities.DesiredCapabilities = {}) { let runner = caps.browserName || caps.appPackage || diff --git a/packages/wdio-cli/src/watcher.ts b/packages/wdio-cli/src/watcher.ts index 95d012a1c89..84c6d1a9eb3 100644 --- a/packages/wdio-cli/src/watcher.ts +++ b/packages/wdio-cli/src/watcher.ts @@ -5,7 +5,7 @@ import flattenDeep from 'lodash.flattendeep' import union from 'lodash.union' import Launcher from './launcher' -import { ConfigOptions } from '@wdio/config' +import type { Options, Capabilities } from '@wdio/types' import { RunCommandArguments } from './types.js' import { EventEmitter } from 'events' @@ -13,17 +13,18 @@ const log = logger('@wdio/cli:watch') export default class Watcher { private _launcher: Launcher - private _args: ConfigOptions private _specs: string[] - constructor (configFile: string, args: ConfigOptions) { + constructor ( + private _configFile: string, + private _args: Omit + ) { log.info('Starting launcher in watch mode') - this._launcher = new Launcher(configFile, args, true) - this._args = args + this._launcher = new Launcher(this._configFile, this._args, true) const specs = this._launcher.configParser.getSpecs() const capSpecs = this._launcher.isMultiremote ? [] : union(flattenDeep( - (this._launcher.configParser.getCapabilities() as WebDriver.DesiredCapabilities[]).map(cap => cap.specs || []) + (this._launcher.configParser.getCapabilities() as Capabilities.DesiredCapabilities[]).map(cap => cap.specs || []) )) this._specs = [...specs, ...capSpecs] } @@ -74,7 +75,7 @@ export default class Watcher { */ getFileListener (passOnFile = true) { return (spec: string) => this.run( - Object.assign({}, this._args, passOnFile ? { spec } : {}) + Object.assign({}, this._args, passOnFile ? { spec: [spec] } : {}) ) } diff --git a/packages/wdio-cli/tests/commands/repl.test.ts b/packages/wdio-cli/tests/commands/repl.test.ts index 4b8f4731167..ee0c41d9f4f 100644 --- a/packages/wdio-cli/tests/commands/repl.test.ts +++ b/packages/wdio-cli/tests/commands/repl.test.ts @@ -20,7 +20,7 @@ jest.mock('repl') describe('repl commandDir', () => { it('should call debug command', async () => { - const client = await handler({ browserName: 'chrome' } as any) as any as WebdriverIO.BrowserObject + const client = await handler({ browserName: 'chrome' } as any) as any expect(client.debug).toHaveBeenCalledTimes(1) expect(client.deleteSession).toHaveBeenCalledTimes(1) }) @@ -59,14 +59,15 @@ describe('Command: repl', () => { it('should set the correct browser', async () => { await handler({ option: 'foobar' } as any) - expect(remote).toHaveBeenCalledWith({ capabilities: { browserName: 'foobar' }, option: 'foobar' }) + expect(remote).toHaveBeenCalledWith({ capabilities: { browserName: 'foobar' }, option: 'foobar' } as any) }) it('should set runner if @wdio/sync is installed', async () => { setSyncSupport(true) await handler({ option: 'foobar' } as any) expect(remote).toHaveBeenCalledWith({ - runner: 'repl', + runner: 'local', + // @ts-expect-error option: 'foobar', capabilities: { browserName: 'foobar' diff --git a/packages/wdio-cli/tests/watcher.test.ts b/packages/wdio-cli/tests/watcher.test.ts index 9f7d40615ff..ce1f5b5835d 100644 --- a/packages/wdio-cli/tests/watcher.test.ts +++ b/packages/wdio-cli/tests/watcher.test.ts @@ -145,8 +145,8 @@ describe('watcher', () => { ;(chokidar.on as jest.Mock).mock.calls[2][1]('/some/another/path.js') // @ts-ignore mock feature ;(chokidar.on as jest.Mock).mock.calls[3][1]('/some/another/path.js') - expect(watcher.run).toHaveBeenNthCalledWith(1, { spec: '/some/path.js' }) - expect(watcher.run).toHaveBeenNthCalledWith(2, { spec: '/some/other/path.js' }) + expect(watcher.run).toHaveBeenNthCalledWith(1, { spec: ['/some/path.js'] }) + expect(watcher.run).toHaveBeenNthCalledWith(2, { spec: ['/some/other/path.js'] }) expect(watcher.run).toHaveBeenNthCalledWith(3, {}) expect(watcher.run).toHaveBeenNthCalledWith(4, {}) }) diff --git a/packages/wdio-cli/tsconfig.json b/packages/wdio-cli/tsconfig.json index a32652092d3..d660189bdb9 100644 --- a/packages/wdio-cli/tsconfig.json +++ b/packages/wdio-cli/tsconfig.json @@ -6,6 +6,7 @@ "rootDir": "./src" }, "include": [ - "src/**/*" + "src/**/*", + "../../@types" ] } diff --git a/packages/wdio-cli/tsconfig.prod.json b/packages/wdio-cli/tsconfig.prod.json index c7e2194a8b4..2a0e7e84140 100644 --- a/packages/wdio-cli/tsconfig.prod.json +++ b/packages/wdio-cli/tsconfig.prod.json @@ -6,6 +6,7 @@ "rootDir": "./src" }, "include": [ - "src/**/*" + "src/**/*", + "../../@types" ] } diff --git a/packages/wdio-concise-reporter/package.json b/packages/wdio-concise-reporter/package.json index 415cbe846ce..c1a5450e41b 100644 --- a/packages/wdio-concise-reporter/package.json +++ b/packages/wdio-concise-reporter/package.json @@ -23,6 +23,7 @@ }, "dependencies": { "@wdio/reporter": "6.11.0", + "@wdio/types": "^6.10.6", "chalk": "^4.0.0", "pretty-ms": "^7.0.0" }, diff --git a/packages/wdio-concise-reporter/src/index.ts b/packages/wdio-concise-reporter/src/index.ts index d4639184f36..1a2573eab35 100644 --- a/packages/wdio-concise-reporter/src/index.ts +++ b/packages/wdio-concise-reporter/src/index.ts @@ -1,13 +1,15 @@ import WDIOReporter, { SuiteStats, RunnerStats } from '@wdio/reporter' import chalk from 'chalk' +import type { Capabilities, Reporters } from '@wdio/types' + export default class ConciseReporter extends WDIOReporter { // keep track of the order that suites were called private _suiteUids: string[] = [] private _suites: SuiteStats[] = [] private _stateCounts = { failed: 0 } - constructor(options: WDIOReporter) { + constructor(options: Reporters.Options) { /** * make Concise reporter to write to output stream by default */ @@ -95,13 +97,12 @@ export default class ConciseReporter extends WDIOReporter { } /** - * Get information about the enviroment - * @param {Object} caps Enviroment details - * @param {Boolean} verbose - * @return {String} Enviroment string - */ - - getEnviromentCombo (caps: WebDriver.DesiredCapabilities) { + * Get information about the enviroment + * @param {Object} caps Enviroment details + * @param {Boolean} verbose + * @return {String} Enviroment string + */ + getEnviromentCombo (caps: Capabilities.DesiredCapabilities) { const device = caps.deviceName const browser = caps.browserName || caps.browser const version = caps.version || caps.platformVersion || caps.browser_version diff --git a/packages/wdio-concise-reporter/tsconfig.json b/packages/wdio-concise-reporter/tsconfig.json index a32652092d3..d660189bdb9 100644 --- a/packages/wdio-concise-reporter/tsconfig.json +++ b/packages/wdio-concise-reporter/tsconfig.json @@ -6,6 +6,7 @@ "rootDir": "./src" }, "include": [ - "src/**/*" + "src/**/*", + "../../@types" ] } diff --git a/packages/wdio-concise-reporter/tsconfig.prod.json b/packages/wdio-concise-reporter/tsconfig.prod.json index c7e2194a8b4..2a0e7e84140 100644 --- a/packages/wdio-concise-reporter/tsconfig.prod.json +++ b/packages/wdio-concise-reporter/tsconfig.prod.json @@ -6,6 +6,7 @@ "rootDir": "./src" }, "include": [ - "src/**/*" + "src/**/*", + "../../@types" ] } diff --git a/packages/wdio-config/package.json b/packages/wdio-config/package.json index 73ea55d7cd3..390e16c11c2 100644 --- a/packages/wdio-config/package.json +++ b/packages/wdio-config/package.json @@ -24,6 +24,7 @@ "types": "./build/index.d.ts", "dependencies": { "@wdio/logger": "6.10.10", + "@wdio/types": "^6.10.6", "deepmerge": "^4.0.0", "glob": "^7.1.2" }, diff --git a/packages/wdio-config/src/constants.ts b/packages/wdio-config/src/constants.ts index cdb772d6fbc..1d32f07987f 100644 --- a/packages/wdio-config/src/constants.ts +++ b/packages/wdio-config/src/constants.ts @@ -1,10 +1,10 @@ -import type { ConfigOptions, Hooks } from './types' +import { Options, Services } from '@wdio/types' const DEFAULT_TIMEOUT = 10000 /* istanbul ignore next */ -export const DEFAULT_CONFIGS: () => ConfigOptions = () => ({ +export const DEFAULT_CONFIGS: () => Omit = () => ({ specs: [], suites: {}, exclude: [], @@ -26,6 +26,12 @@ export const DEFAULT_CONFIGS: () => ConfigOptions = () => ({ execArgv: [], runnerEnv: {}, runner: 'local' as const, + specFileRetries: 0, + specFileRetriesDelay: 0, + specFileRetriesDeferred: false, + reporterSyncInterval: 100, + reporterSyncTimeout: 5000, + cucumberFeaturesWithLineNumbers: [], /** * framework defaults @@ -68,12 +74,13 @@ export const DEFAULT_CONFIGS: () => ConfigOptions = () => ({ beforeStep: [], afterStep: [], afterScenario: [], - afterFeature: [], + afterFeature: [] }) -export const SUPPORTED_HOOKS: (keyof Hooks)[] = [ +export const SUPPORTED_HOOKS: (keyof Services.Hooks)[] = [ 'before', 'beforeSession', 'beforeSuite', 'beforeHook', 'beforeTest', 'beforeCommand', 'afterCommand', 'afterTest', 'afterHook', 'afterSuite', 'afterSession', 'after', + // @ts-ignore not defined in core hooks but added with cucumber 'beforeFeature', 'beforeScenario', 'beforeStep', 'afterStep', 'afterScenario', 'afterFeature', 'onReload', 'onPrepare', 'onWorkerStart', 'onComplete' ] diff --git a/packages/wdio-config/src/index.ts b/packages/wdio-config/src/index.ts index 98e7d0cc1ac..2b6ef65e6f3 100644 --- a/packages/wdio-config/src/index.ts +++ b/packages/wdio-config/src/index.ts @@ -3,16 +3,13 @@ import ConfigParser from './lib/ConfigParser' import { validateConfig, getSauceEndpoint, detectBackend, isCloudCapability } from './utils' import { DEFAULT_CONFIGS } from './constants' -import { ConfigOptions } from './types' -export * from './types' export { validateConfig, getSauceEndpoint, detectBackend, isCloudCapability, ConfigParser, - ConfigOptions, /** * constants diff --git a/packages/wdio-config/src/lib/ConfigParser.ts b/packages/wdio-config/src/lib/ConfigParser.ts index 3821179c827..3c062e93570 100644 --- a/packages/wdio-config/src/lib/ConfigParser.ts +++ b/packages/wdio-config/src/lib/ConfigParser.ts @@ -3,25 +3,32 @@ import path from 'path' import glob from 'glob' import merge from 'deepmerge' import logger from '@wdio/logger' +import type { Capabilities, Options, Services } from '@wdio/types' import { detectBackend, removeLineNumbers, isCucumberFeatureWithLineNumber, validObjectOrArray, loadTypeScriptCompiler, loadBabelCompiler } from '../utils' import { DEFAULT_CONFIGS, SUPPORTED_HOOKS, SUPPORTED_FILE_EXTENSIONS } from '../constants' -import type { Capabilities, ConfigOptions, Hooks } from '../types' const log = logger('@wdio/config:ConfigParser') const MERGE_OPTIONS = { clone: false } -interface MergeConfig extends Omit, 'specs' | 'exclude'> { +interface TestrunnerOptionsWithParameters extends Omit { + watch?: boolean + spec?: string[] + suite?: string[] + capabilities?: Capabilities.RemoteCapabilities +} + +interface MergeConfig extends Omit, 'specs' | 'exclude'> { specs?: string | string[] exclude?: string | string[] } export default class ConfigParser { - private _config: ConfigOptions = DEFAULT_CONFIGS() - private _capabilities: Capabilities = []; + private _config: TestrunnerOptionsWithParameters = DEFAULT_CONFIGS() + private _capabilities: Capabilities.RemoteCapabilities = []; /** * merges config file with default values @@ -45,14 +52,13 @@ export default class ConfigParser { /** * clone the original config */ - const fileConfig = merge(require(filePath).config, {}, MERGE_OPTIONS) + const fileConfig = merge(require(filePath).config, {}, MERGE_OPTIONS) /** * merge capabilities */ - const defaultTo: Capabilities = Array.isArray(this._capabilities) ? [] : {} - this._capabilities = merge(this._capabilities, fileConfig.capabilities || defaultTo, MERGE_OPTIONS) - delete fileConfig.capabilities + const defaultTo: Capabilities.RemoteCapabilities = Array.isArray(this._capabilities) ? [] : {} + this._capabilities = merge(this._capabilities, fileConfig.capabilities || defaultTo, MERGE_OPTIONS) /** * Add hooks from the file config and remove them from file config object to avoid @@ -87,8 +93,8 @@ export default class ConfigParser { merge (object: MergeConfig = {}) { const spec = Array.isArray(object.spec) ? object.spec : [] const exclude = Array.isArray(object.exclude) ? object.exclude : [] + this._config = { ...this._config, ...object } as Options.Testrunner - this._config = merge(this._config, object, MERGE_OPTIONS) as ConfigOptions /** * overwrite config specs that got piped into the wdio command */ @@ -129,9 +135,9 @@ export default class ConfigParser { * Add hooks from an existing service to the runner config. * @param {Object} service - an object that contains hook methods. */ - addService (service: Hooks) { - const addHook = (hookName: T, hook: Extract) => { - const existingHooks: ConfigOptions[keyof Hooks] = this._config[hookName] + addService (service: Services.Hooks) { + const addHook = (hookName: T, hook: Extract) => { + const existingHooks: Options.Testrunner[keyof Services.Hooks] = this._config[hookName] if (!existingHooks) { // @ts-ignore Expression produces a union type that is too complex to represent this._config[hookName] = hook.bind(service) @@ -253,7 +259,7 @@ export default class ConfigParser { * return configs */ getConfig () { - return this._config as Required + return this._config as Required } /** diff --git a/packages/wdio-config/src/types.ts b/packages/wdio-config/src/types.ts deleted file mode 100644 index 81259bd744f..00000000000 --- a/packages/wdio-config/src/types.ts +++ /dev/null @@ -1,33 +0,0 @@ - -export type Hooks = { - [k in keyof WebdriverIO.HookFunctions]: WebdriverIO.HookFunctions[k] | NonNullable[]; -} - -export type Capabilities = (WebDriver.DesiredCapabilities | WebDriver.W3CCapabilities)[] | WebdriverIO.MultiRemoteCapabilities; -export type Capability = WebDriver.DesiredCapabilities | WebDriver.W3CCapabilities | WebdriverIO.MultiRemoteCapabilities; - -export interface ConfigOptions extends Omit, Hooks { - automationProtocol?: 'webdriver' | 'devtools' | './protocol-stub' - spec?: string[] - suite?: string[] - capabilities?: Capabilities - specFileRetryAttempts?: number - cucumberFeaturesWithLineNumbers?: string[] - specFileRetriesDeferred?: boolean - specFileRetries?: number - maxInstances?: number -} - -export interface SingleConfigOption extends Omit { - capabilities: Capability -} - -export type DefaultOptions = { - [k in keyof T]?: { - type: 'string' | 'number' | 'object' | 'boolean' | 'function' - default?: T[k] - required?: boolean - validate?: (option: T[k]) => void - match?: RegExp - } -} diff --git a/packages/wdio-config/src/utils.ts b/packages/wdio-config/src/utils.ts index 32637b52138..23bff081839 100644 --- a/packages/wdio-config/src/utils.ts +++ b/packages/wdio-config/src/utils.ts @@ -1,6 +1,5 @@ import logger from '@wdio/logger' - -import type { DefaultOptions, Capabilities } from './types' +import type { Capabilities, Options } from '@wdio/types' const log = logger('@wdio/config:utils') @@ -58,8 +57,7 @@ export function isCucumberFeatureWithLineNumber(spec: string | string[]) { return specs.some((s) => s.match(/:\d+(:\d+$|$)/)) } -export function isCloudCapability(capabilities: WebDriver.DesiredCapabilities | WebdriverIO.MultiRemoteBrowserOptions) { - const caps = (capabilities as WebdriverIO.MultiRemoteBrowserOptions).capabilities || capabilities +export function isCloudCapability(caps: Capabilities.Capabilities) { return Boolean(caps && (caps['bstack:options'] || caps['sauce:options'] || caps['tb:options'])) } @@ -72,7 +70,7 @@ interface BackendConfigurations { region?: string headless?: boolean path?: string - capabilities?: Capabilities | WebDriver.DesiredCapabilities | WebDriver.W3CCapabilities + capabilities?: Capabilities.RemoteCapabilities | Capabilities.RemoteCapability } /** @@ -173,10 +171,10 @@ export function detectBackend(options: BackendConfigurations = {}) { * @param {Object} options option to check against * @return {Object} validated config enriched with default values */ -export function validateConfig(defaults: DefaultOptions, options: T, keysToKeep = [] as (keyof T)[]) { +export function validateConfig(defaults: Options.Definition, options: T, keysToKeep = [] as (keyof T)[]) { const params = {} as T - for (const [name, expectedOption] of Object.entries(defaults) as [keyof DefaultOptions, NonNullable[keyof DefaultOptions]>][]) { + for (const [name, expectedOption] of Object.entries(defaults) as [keyof Options.Definition, NonNullable[keyof Options.Definition]>][]) { /** * check if options is given */ diff --git a/packages/wdio-config/tests/utils.test.ts b/packages/wdio-config/tests/utils.test.ts index 2301326402d..df164ed358c 100644 --- a/packages/wdio-config/tests/utils.test.ts +++ b/packages/wdio-config/tests/utils.test.ts @@ -63,28 +63,5 @@ describe('utils', () => { it('should handle null or empty capabilities', () => { expect(isCloudCapability({})).toBe(false) }) - - describe('same thing but now multiremote options', () => { - it('should detect Browserstack capabilities', () => { - expect(isCloudCapability({ capabilities: { 'bstack:options': {} } })).toBe(true) - }) - - it('should detect Saucelabs capabilities', () => { - expect(isCloudCapability({ capabilities: { 'sauce:options': {} } })).toBe(true) - }) - - it('should detect Testingbot capabilities', () => { - expect(isCloudCapability({ capabilities: { 'tb:options': {} } })).toBe(true) - }) - - it('should detect non-cloud capabilities', () => { - expect(isCloudCapability({ capabilities: { 'selenoid:options': {} } })).toBe(false) - }) - - it('should handle null or empty capabilities', () => { - expect(isCloudCapability({ capabilities: null })).toBe(false) - expect(isCloudCapability({ capabilities: {} })).toBe(false) - }) - }) }) }) diff --git a/packages/wdio-config/tsconfig.json b/packages/wdio-config/tsconfig.json index 92656749c08..d660189bdb9 100644 --- a/packages/wdio-config/tsconfig.json +++ b/packages/wdio-config/tsconfig.json @@ -1,12 +1,12 @@ { - "extends": "../../tsconfig", - "compilerOptions": { - "baseUrl": ".", - "outDir": "./build", - "rootDir": "./src", - "types": ["webdriverio", "webdriver", "@wdio/cucumber-framework"] - }, - "include": [ - "src/**/*" - ] + "extends": "../../tsconfig", + "compilerOptions": { + "baseUrl": ".", + "outDir": "./build", + "rootDir": "./src" + }, + "include": [ + "src/**/*", + "../../@types" + ] } diff --git a/packages/wdio-config/tsconfig.prod.json b/packages/wdio-config/tsconfig.prod.json index 33c5a8efcb7..2a0e7e84140 100644 --- a/packages/wdio-config/tsconfig.prod.json +++ b/packages/wdio-config/tsconfig.prod.json @@ -1,12 +1,12 @@ { - "extends": "../../tsconfig.prod", - "compilerOptions": { - "baseUrl": ".", - "outDir": "./build", - "rootDir": "./src", - "types": ["webdriverio", "webdriver", "@wdio/cucumber-framework"] - }, - "include": [ - "src/**/*" - ] + "extends": "../../tsconfig.prod", + "compilerOptions": { + "baseUrl": ".", + "outDir": "./build", + "rootDir": "./src" + }, + "include": [ + "src/**/*", + "../../@types" + ] } diff --git a/packages/wdio-crossbrowsertesting-service/package.json b/packages/wdio-crossbrowsertesting-service/package.json index 8d1d5b87a0f..e595c206e1d 100644 --- a/packages/wdio-crossbrowsertesting-service/package.json +++ b/packages/wdio-crossbrowsertesting-service/package.json @@ -23,8 +23,10 @@ }, "dependencies": { "@wdio/logger": "6.10.10", + "@wdio/types": "^6.10.6", "cbt_tunnels": "^1.2.2", - "got": "^11.0.2" + "got": "^11.0.2", + "webdriverio": "^6.11.3" }, "peerDependencies": { "@wdio/cli": "^6.0.1" @@ -32,5 +34,5 @@ "publishConfig": { "access": "public" }, - "types": "crossbrowsertesting-service.d.ts" + "types": "./build/index.d.ts" } diff --git a/packages/wdio-crossbrowsertesting-service/src/index.ts b/packages/wdio-crossbrowsertesting-service/src/index.ts index b053702f144..f2b60b80b9e 100644 --- a/packages/wdio-crossbrowsertesting-service/src/index.ts +++ b/packages/wdio-crossbrowsertesting-service/src/index.ts @@ -2,6 +2,14 @@ import CrossBrowserTestingLauncher from './launcher' import CrossBrowserTestingService from './service' +import { CrossBrowserTestingConfig } from './types' export default CrossBrowserTestingService export const launcher = CrossBrowserTestingLauncher +export * from './types' + +declare global { + namespace WebdriverIO { + interface ServiceOption extends CrossBrowserTestingConfig {} + } +} diff --git a/packages/wdio-crossbrowsertesting-service/src/launcher.ts b/packages/wdio-crossbrowsertesting-service/src/launcher.ts index 272d65fe078..220cbc3c3c2 100644 --- a/packages/wdio-crossbrowsertesting-service/src/launcher.ts +++ b/packages/wdio-crossbrowsertesting-service/src/launcher.ts @@ -1,19 +1,22 @@ import { promisify } from 'util' import { performance, PerformanceObserver } from 'perf_hooks' +import type { Capabilities, Services, Options } from '@wdio/types' import cbt from 'cbt_tunnels' import logger from '@wdio/logger' +import { CrossBrowserTestingConfig } from './types' + const log = logger('@wdio/crossbrowsertesting-service') -export default class CrossBrowserTestingLauncher implements WebdriverIO.ServiceInstance { +export default class CrossBrowserTestingLauncher implements Services.ServiceInstance { private _isUsingTunnel: boolean = false; private _cbtTunnelOpts: CBTConfigInterface; constructor ( - private _options: WebdriverIO.ServiceOption, - private _caps: WebDriver.DesiredCapabilities, - private _config: WebdriverIO.Config + private _options: CrossBrowserTestingConfig, + private _caps: Capabilities.Capabilities, + private _config: Options.Testrunner ) { this._cbtTunnelOpts = Object.assign({ username: this._config.user, diff --git a/packages/wdio-crossbrowsertesting-service/src/service.ts b/packages/wdio-crossbrowsertesting-service/src/service.ts index e209bacfab8..820249ede62 100644 --- a/packages/wdio-crossbrowsertesting-service/src/service.ts +++ b/packages/wdio-crossbrowsertesting-service/src/service.ts @@ -1,11 +1,13 @@ import got from 'got' import logger from '@wdio/logger' +import type { Capabilities, Services, Options, Frameworks } from '@wdio/types' +import type { Browser, MultiRemoteBrowser } from 'webdriverio' const log = logger('@wdio/crossbrowsertesting-service') const jobDataProperties = ['name', 'tags', 'public', 'build', 'extra'] -export default class CrossBrowserTestingService implements WebdriverIO.ServiceInstance { - private _browser!: WebdriverIO.BrowserObject | WebdriverIO.MultiRemoteBrowserObject +export default class CrossBrowserTestingService implements Services.ServiceInstance { + private _browser?: Browser<'async'> | MultiRemoteBrowser<'async'> private _testCnt = 0; private _failures = 0; private _isServiceEnabled: boolean; @@ -14,8 +16,8 @@ export default class CrossBrowserTestingService implements WebdriverIO.ServiceIn private _cbtAuthkey: string; constructor( - private _config: WebdriverIO.Options, - private _capabilities: WebDriver.DesiredCapabilities + private _config: Options.Testrunner, + private _capabilities: Capabilities.Capabilities ) { this._cbtUsername = this._config.user as string this._cbtAuthkey = this._config.key as string @@ -23,9 +25,9 @@ export default class CrossBrowserTestingService implements WebdriverIO.ServiceIn } before ( - caps: WebDriver.Capabilities, + caps: Capabilities.Capabilities, specs: string[], - browser: WebdriverIO.BrowserObject | WebdriverIO.MultiRemoteBrowserObject + browser: Browser<'async'> | MultiRemoteBrowser<'async'> ) { this._browser = browser } @@ -34,7 +36,7 @@ export default class CrossBrowserTestingService implements WebdriverIO.ServiceIn * Before suite * @param {Object} suite Suite */ - beforeSuite (suite: WebdriverIO.Suite) { + beforeSuite (suite: Frameworks.Suite) { this._suiteTitle = suite.title } @@ -42,7 +44,7 @@ export default class CrossBrowserTestingService implements WebdriverIO.ServiceIn * Before test * @param {Object} test Test */ - beforeTest (test: WebdriverIO.Test) { + beforeTest (test: Frameworks.Test) { if (!this._isServiceEnabled) { return } @@ -58,7 +60,7 @@ export default class CrossBrowserTestingService implements WebdriverIO.ServiceIn } } - afterSuite (suite: WebdriverIO.Suite) { + afterSuite (suite: Frameworks.Suite) { if (Object.prototype.hasOwnProperty.call(suite, 'error')) { ++this._failures } @@ -68,7 +70,7 @@ export default class CrossBrowserTestingService implements WebdriverIO.ServiceIn * After test * @param {Object} test Test */ - afterTest (test: WebdriverIO.Test, context: any, results: WebdriverIO.TestResult) { + afterTest (test: Frameworks.Test, context: any, results: Frameworks.TestResult) { if (!results.passed) { ++this._failures } @@ -76,29 +78,25 @@ export default class CrossBrowserTestingService implements WebdriverIO.ServiceIn /** * For CucumberJS - */ - - /** + * * Before feature * @param {string} uri * @param {Object} feature */ - beforeFeature (uri: string, feature: any) { + beforeFeature (uri: unknown, feature: { name: string }) { if (!this._isServiceEnabled) { return } - this._suiteTitle = feature.document.feature.name + this._suiteTitle = feature.name } /** * After step - * @param {string} uri - * @param {Object} feature - * @param {Object} pickle - * @param {Object} result + * @param {world} world */ - afterScenario(uri: string, feature: any, pickle: any, result: any) { - if (result.status === 'failed') { + afterScenario(world: Frameworks.World) { + // check if scenario has failed + if (world.result && world.result.status === 6) { ++this._failures } } @@ -108,7 +106,7 @@ export default class CrossBrowserTestingService implements WebdriverIO.ServiceIn * @return {Promise} Promsie with result of updateJob method call */ after (result?: number) { - if (!this._isServiceEnabled) { + if (!this._isServiceEnabled || !this._browser) { return } @@ -118,7 +116,7 @@ export default class CrossBrowserTestingService implements WebdriverIO.ServiceIn * set failures if user has bail option set in which case afterTest and * afterSuite aren't executed before after hook */ - if (this._browser.config.mochaOpts && this._browser.config.mochaOpts.bail && Boolean(result)) { + if (this._config.mochaOpts && this._config.mochaOpts.bail && Boolean(result)) { failures = 1 } @@ -136,7 +134,7 @@ export default class CrossBrowserTestingService implements WebdriverIO.ServiceIn } onReload (oldSessionId: string, newSessionId: string) { - if (!this._isServiceEnabled) { + if (!this._isServiceEnabled || !this._browser) { return } const status = 'status: ' + (this._failures > 0 ? 'failing' : 'passing') @@ -147,7 +145,7 @@ export default class CrossBrowserTestingService implements WebdriverIO.ServiceIn } const browserName = this._browser.instances.filter( - (browserName) => (this._browser as WebdriverIO.MultiRemoteBrowserObject)[browserName].sessionId === newSessionId)[0] + (browserName) => (this._browser as MultiRemoteBrowser<'async'>)[browserName].sessionId === newSessionId)[0] log.info(`Update (reloaded) multiremote job for browser "${browserName}" and sessionId ${oldSessionId}, ${status}`) return this.updateJob(oldSessionId, this._failures, true, browserName) } @@ -184,7 +182,7 @@ export default class CrossBrowserTestingService implements WebdriverIO.ServiceIn /** * add reload count to title if reload is used */ - if (calledOnReload || this._testCnt) { + if ((calledOnReload || this._testCnt) && this._browser) { let testCnt = ++this._testCnt if (this._browser.isMultiremote) { testCnt = Math.ceil(testCnt / this._browser.instances.length) diff --git a/packages/wdio-crossbrowsertesting-service/crossbrowsertesting-service.d.ts b/packages/wdio-crossbrowsertesting-service/src/types.ts similarity index 66% rename from packages/wdio-crossbrowsertesting-service/crossbrowsertesting-service.d.ts rename to packages/wdio-crossbrowsertesting-service/src/types.ts index d5865547fd2..5d34c37288b 100644 --- a/packages/wdio-crossbrowsertesting-service/crossbrowsertesting-service.d.ts +++ b/packages/wdio-crossbrowsertesting-service/src/types.ts @@ -1,8 +1,4 @@ -declare module WebdriverIO { - interface ServiceOption extends CrossBrowserTestingConfig {} -} - -interface CrossBrowserTestingConfig { +export interface CrossBrowserTestingConfig { /** * If true secure CBT local connection is started. */ diff --git a/packages/wdio-crossbrowsertesting-service/tests/launcher.test.ts b/packages/wdio-crossbrowsertesting-service/tests/launcher.test.ts index 0082a3a5d2b..841c2b54cc4 100644 --- a/packages/wdio-crossbrowsertesting-service/tests/launcher.test.ts +++ b/packages/wdio-crossbrowsertesting-service/tests/launcher.test.ts @@ -1,8 +1,11 @@ import cbtTunnels from 'cbt_tunnels' import logger from '@wdio/logger' +import { Capabilities, Options } from '@wdio/types' import CrossBrowserTestingLauncher from '../src/launcher' +import { CrossBrowserTestingConfig } from '../src/types' +const expect = global.expect as unknown as jest.Expect const error = new Error('Error!') describe('wdio-crossbrowsertesting-service', () => { @@ -11,11 +14,11 @@ describe('wdio-crossbrowsertesting-service', () => { it('onPrepare: cbtTunnel is undefined', async () => { const options = { cbtTunnel: undefined } as any - const caps: WebDriver.DesiredCapabilities = {} + const caps: Capabilities.DesiredCapabilities = {} const config = { user: 'test', key: 'key' - } + } as Options.Testrunner const cbtLauncher = new CrossBrowserTestingLauncher(options, caps, config) @@ -35,10 +38,10 @@ describe('wdio-crossbrowsertesting-service', () => { options: 'some options' } } - const cbtLauncher = new CrossBrowserTestingLauncher(options, [{}] as WebDriver.DesiredCapabilities, { + const cbtLauncher = new CrossBrowserTestingLauncher(options, [{}] as Capabilities.DesiredCapabilities, { user: 'test', key: 'testy' - }) + } as any) await cbtLauncher.onPrepare() expect(cbtTunnels.start).toHaveBeenCalledWith({ username: 'test', authkey: 'testy', nokill: true, options: 'some options' }, expect.any(Function)) expect(cbtLauncher['_cbtTunnelOpts']).toEqual({ username: 'test', authkey: 'testy', nokill: true, options: 'some options' }) @@ -53,24 +56,24 @@ describe('wdio-crossbrowsertesting-service', () => { options: 'some options' } } - const cbtLauncher = new CrossBrowserTestingLauncher(options, [{}] as WebDriver.DesiredCapabilities, { + const cbtLauncher = new CrossBrowserTestingLauncher(options, [{}] as Capabilities.DesiredCapabilities, { user: 'test', key: 'testy' - }); - (cbtTunnels.start as jest.Mock).mockImplementationOnce((options: CBTConfigInterface, cb: any) => cb(error)) + } as any); + (cbtTunnels.start as jest.Mock).mockImplementationOnce((options: never, cb: any) => cb(error)) expect(cbtLauncher.onPrepare()).rejects.toThrow(error) .then(() => expect(cbtTunnels.start).toHaveBeenCalled()) }) it('onComplete: no tunnel', () => { - const cbtLauncher = new CrossBrowserTestingLauncher({}, [{}] as WebDriver.DesiredCapabilities, {}) + const cbtLauncher = new CrossBrowserTestingLauncher({}, [{}] as Capabilities.DesiredCapabilities, {} as any) cbtLauncher['_isUsingTunnel'] = false expect(cbtLauncher.onComplete()).toBeUndefined() }) it('onComplete: cbtTunnel.stop throws an error', () => { - const cbtLauncher = new CrossBrowserTestingLauncher({} as any, [{}] as WebDriver.DesiredCapabilities, {}) + const cbtLauncher = new CrossBrowserTestingLauncher({} as any, [{}] as Capabilities.DesiredCapabilities, {} as any) cbtLauncher['_isUsingTunnel'] = true; (cbtTunnels.stop as jest.Mock).mockImplementationOnce((cb: any) => cb(error)) expect(cbtLauncher.onComplete()).rejects.toThrow(error) @@ -78,7 +81,7 @@ describe('wdio-crossbrowsertesting-service', () => { }) it('onComplete: cbtTunnel.stop successful', async () => { - const cbtLauncher = new CrossBrowserTestingLauncher({} as any, [{}] as WebDriver.DesiredCapabilities, {}) + const cbtLauncher = new CrossBrowserTestingLauncher({} as any, [{}] as Capabilities.DesiredCapabilities, {} as any) cbtLauncher['_isUsingTunnel'] = true expect(cbtLauncher.onComplete()).resolves.toBe('stopped') .then(() => expect(cbtTunnels.stop).toHaveBeenCalled()) diff --git a/packages/wdio-crossbrowsertesting-service/tests/service.test.ts b/packages/wdio-crossbrowsertesting-service/tests/service.test.ts index ba59854b4df..292b40c3488 100644 --- a/packages/wdio-crossbrowsertesting-service/tests/service.test.ts +++ b/packages/wdio-crossbrowsertesting-service/tests/service.test.ts @@ -1,28 +1,12 @@ -import CrossBrowserTestingService from '../src/service' import got from 'got' +import CrossBrowserTestingService from '../src/service' +import { Frameworks } from '@wdio/types' (got.put as jest.Mock).mockReturnValue(Promise.resolve({ body: '{}' })) const uri = 'some/uri' const featureObject = { - type: 'gherkin-document', - uri: '__tests__/features/passed.feature', - document: - { - type: 'GherkinDocument', - feature: - { - type: 'Feature', - tags: ['tag'], - location: ['Object'], - language: 'en', - keyword: 'Feature', - name: 'Create a feature', - description: ' the description', - children: [''], - }, - comments: [] - } + name: 'Create a feature' } const testArgumens = { @@ -33,7 +17,7 @@ const testArgumens = { build: 344 }, beforeSession: { user: 'test', key: 'testy' }, - afterTest:<[WebdriverIO.Test, any]> [{}, {}] + afterTest:<[Frameworks.Test, any]> [{}, {}] } describe('wdio-crossbrowsertesting-service', () => { @@ -74,10 +58,10 @@ describe('wdio-crossbrowsertesting-service', () => { } const cbtService = new CrossBrowserTestingService({ ...testArgumens.beforeSession - }, testArgumens.capabilities) + } as any, testArgumens.capabilities as any) cbtService['_browser'] = browser - expect(cbtService['_capabilities']).toEqual(capabilities) + expect(cbtService['_capabilities']).toEqual(capabilities as any) expect(cbtService['_cbtUsername']).toEqual('test') expect(cbtService['_cbtAuthkey']).toEqual('testy') expect(cbtService['_testCnt']).toEqual(0) @@ -88,28 +72,30 @@ describe('wdio-crossbrowsertesting-service', () => { const cbtService = new CrossBrowserTestingService( { ...testArgumens.beforeSession - }, - testArgumens.capabilities + } as any, + testArgumens.capabilities as any ) cbtService.before({}, [], 'browser' as any) - expect(cbtService['_browser']).toBe('browser') + expect(cbtService['_browser']).toBe('browser' as any) }) it('beforeSuite', () => { - const cbtService = new CrossBrowserTestingService({ - ...testArgumens.beforeSession - }, testArgumens.capabilities) + const cbtService = new CrossBrowserTestingService( + { ...testArgumens.beforeSession } as any, + testArgumens.capabilities as any + ) cbtService['_browser'] = browser const suiteTitle = 'Test Suite Title' - cbtService.beforeSuite({ title: suiteTitle } as WebdriverIO.Suite) + cbtService.beforeSuite({ title: suiteTitle } as Frameworks.Suite) expect(cbtService['_suiteTitle']).toEqual(suiteTitle) }) it('beforeTest: execute not called', () => { - const cbtService = new CrossBrowserTestingService({ - ...testArgumens.beforeSession - }, testArgumens.capabilities) + const cbtService = new CrossBrowserTestingService( + { ...testArgumens.beforeSession } as any, + testArgumens.capabilities as any + ) cbtService['_browser'] = browser const test = { fullName: 'Test #1', @@ -125,7 +111,7 @@ describe('wdio-crossbrowsertesting-service', () => { it('beforeTest: execute called', () => { const cbtService = new CrossBrowserTestingService({ ...testArgumens.beforeSession - }, {}) + } as any, {}) cbtService['_browser'] = browser const test = { name: 'Test name', @@ -133,7 +119,7 @@ describe('wdio-crossbrowsertesting-service', () => { title: 'Test title', parent: 'Test parent' } as any - cbtService.beforeSuite({ title: 'Test suite' } as WebdriverIO.Suite) + cbtService.beforeSuite({ title: 'Test suite' } as Frameworks.Suite) cbtService.beforeTest(test) expect(cbtService['_suiteTitle']).toEqual('Test suite') @@ -142,7 +128,7 @@ describe('wdio-crossbrowsertesting-service', () => { it('beforeTest: execute called', () => { const cbtService = new CrossBrowserTestingService({ ...testArgumens.beforeSession - }, {}) + } as any, {}) cbtService['_browser'] = browser const test = { name: 'Test name', @@ -151,59 +137,59 @@ describe('wdio-crossbrowsertesting-service', () => { parent: 'Test parent' } as any - cbtService.beforeSuite({ title: 'Jasmine__TopLevel__Suite' } as WebdriverIO.Suite) + cbtService.beforeSuite({ title: 'Jasmine__TopLevel__Suite' } as Frameworks.Suite) cbtService.beforeTest(test) expect(cbtService['_suiteTitle']).toEqual('Test ') }) it('beforeTest: should not do anything if no key was specified', () => { - const cbtService = new CrossBrowserTestingService({}, {}) - cbtService.beforeSuite({ title: 'Jasmine__TopLevel__Suite' } as WebdriverIO.Suite) - cbtService.beforeTest({}) + const cbtService = new CrossBrowserTestingService({} as any, {} as any) + cbtService.beforeSuite({ title: 'Jasmine__TopLevel__Suite' } as Frameworks.Suite) + cbtService.beforeTest({} as any) expect(cbtService['_suiteTitle']).not.toEqual('Test ') }) it('afterSuite', ()=>{ - const cbtService = new CrossBrowserTestingService({}, {}) + const cbtService = new CrossBrowserTestingService({} as any, {} as any) cbtService['_browser'] = browser expect(cbtService['_failures']).toBe(0) - cbtService.afterSuite({} as WebdriverIO.Suite) + cbtService.afterSuite({} as Frameworks.Suite) expect(cbtService['_failures']).toBe(0) - cbtService.afterSuite({ error: new Error('error') } as WebdriverIO.Suite) + cbtService.afterSuite({ error: new Error('error') } as Frameworks.Suite) expect(cbtService['_failures']).toBe(1) }) it('afterTest: passed test', () => { - const cbtService = new CrossBrowserTestingService({}, {}) + const cbtService = new CrossBrowserTestingService({} as any, {} as any) cbtService['_browser'] = browser cbtService['_failures'] = 0 const testResult = { passed: true - } as WebdriverIO.TestResult + } as Frameworks.TestResult cbtService.afterTest(...testArgumens.afterTest, testResult) expect(cbtService['_failures']).toEqual(0) }) it('afterTest: failed test', () => { - const cbtService = new CrossBrowserTestingService({}, {}) + const cbtService = new CrossBrowserTestingService({} as any, {} as any) cbtService['_browser'] = browser cbtService['_failures'] = 0 const testResult = { passed: false - } as WebdriverIO.TestResult + } as Frameworks.TestResult cbtService.afterTest(...testArgumens.afterTest, testResult) expect(cbtService['_failures']).toEqual(1) }) it('beforeFeature: execute not called', () => { - const cbtService = new CrossBrowserTestingService({}, {}) + const cbtService = new CrossBrowserTestingService({} as any, {} as any) cbtService['_browser'] = browser cbtService.beforeFeature(uri, featureObject) }) @@ -211,7 +197,7 @@ describe('wdio-crossbrowsertesting-service', () => { it('beforeFeature: execute called', () => { const cbtService = new CrossBrowserTestingService({ ...testArgumens.beforeSession - }, {}) + } as any, {}) cbtService['_browser'] = browser cbtService.beforeFeature(uri, featureObject) @@ -219,22 +205,22 @@ describe('wdio-crossbrowsertesting-service', () => { }) it('afterScenario: exception happened', () => { - const cbtService = new CrossBrowserTestingService({}, {}) + const cbtService = new CrossBrowserTestingService({} as any, {} as any) cbtService['_browser'] = browser cbtService['_failures'] = 0 expect(cbtService['_failures']).toBe(0) - cbtService.afterScenario(uri, {}, {}, { status: 'passed' }) + cbtService.afterScenario({ pickle: {}, result: { status: 1 } }) expect(cbtService['_failures']).toBe(0) - cbtService.afterScenario(uri, {}, {}, { status: 'failed' }) + cbtService.afterScenario({ pickle: {}, result: { status: 6 } }) expect(cbtService['_failures']).toBe(1) - cbtService.afterScenario(uri, {}, {}, { status: 'passed' }) + cbtService.afterScenario({ pickle: {}, result: { status: 1 } }) expect(cbtService['_failures']).toBe(1) - cbtService.afterScenario(uri, {}, {}, { status: 'failed' }) + cbtService.afterScenario({ pickle: {}, result: { status: 6 } }) expect(cbtService['_failures']).toBe(2) }) @@ -242,47 +228,47 @@ describe('wdio-crossbrowsertesting-service', () => { const cbtService = new CrossBrowserTestingService({ user: '', key: '' - }, {}) + } as any, {} as any) cbtService['_browser'] = browser const updateJobSpy = jest.spyOn(cbtService, 'updateJob') await cbtService.after() - expect(updateJobSpy).not.toBeCalled() + expect(updateJobSpy).toHaveBeenCalledTimes(0) }) it('after: updatedJob called with passed params', async () => { const cbtService = new CrossBrowserTestingService({ ...testArgumens.beforeSession - }, {}) + } as any, {}) cbtService['_browser'] = browser const updateJobSpy = jest.spyOn(cbtService, 'updateJob') - cbtService['_browser'].config = { mochaOpts: { bail: 1 } } + cbtService['_config'] = { mochaOpts: { bail: 1 } } as any cbtService['_browser'].sessionId = 'test' cbtService['_failures'] = 2 await cbtService.after() - expect(updateJobSpy).toBeCalledWith('test', 2) + expect(updateJobSpy).toHaveBeenCalledWith('test', 2) }) test('after: with bail set', async () => { - const cbtService = new CrossBrowserTestingService({ ...testArgumens.beforeSession }, {}) + const cbtService = new CrossBrowserTestingService({ ...testArgumens.beforeSession } as any, {}) cbtService['_browser'] = browser cbtService['_failures'] = 5 cbtService.updateJob = jest.fn() cbtService['_browser'].isMultiremote = false cbtService['_browser'].sessionId = 'test' - cbtService['_browser'].config = { mochaOpts: { bail: 1 } } + cbtService['_config'] = { mochaOpts: { bail: 1 } } as any await cbtService.after(1) - expect(cbtService.updateJob).toBeCalledWith('test', 1) + expect(cbtService.updateJob).toHaveBeenCalledWith('test', 1) }) it('after: with multi-remote: updatedJob called with passed params', async () => { const cbtService = new CrossBrowserTestingService({ ...testArgumens.beforeSession - }, { chromeA: {}, chromeB: {}, chromeC: {} } as any) + } as any, { chromeA: {}, chromeB: {}, chromeC: {} } as any) cbtService['_browser'] = browser const updateJobSpy = jest.spyOn(cbtService, 'updateJob') cbtService['_browser'].isMultiremote = true @@ -290,30 +276,30 @@ describe('wdio-crossbrowsertesting-service', () => { cbtService['_failures'] = 2 await cbtService.after() - expect(updateJobSpy).toBeCalledWith('sessionChromeA', 2, false, 'chromeA') - expect(updateJobSpy).toBeCalledWith('sessionChromeB', 2, false, 'chromeB') - expect(updateJobSpy).toBeCalledWith('sessionChromeC', 2, false, 'chromeC') + expect(updateJobSpy).toHaveBeenCalledWith('sessionChromeA', 2, false, 'chromeA') + expect(updateJobSpy).toHaveBeenCalledWith('sessionChromeB', 2, false, 'chromeB') + expect(updateJobSpy).toHaveBeenCalledWith('sessionChromeC', 2, false, 'chromeC') }) it('onReload: updatedJob not called', () => { const cbtService = new CrossBrowserTestingService({ user: undefined, key: undefined - }, {}) + } as any, {}) cbtService['_browser'] = browser - const cbtService2 = new CrossBrowserTestingService({}, {}) + const cbtService2 = new CrossBrowserTestingService({} as any, {} as any) const updateJobSpy = jest.spyOn(cbtService2, 'updateJob') cbtService['_browser'].sessionId = 'sessionId' cbtService.onReload('oldSessionId', 'newSessionId') - expect(updateJobSpy).not.toBeCalled() + expect(updateJobSpy).toHaveBeenCalledTimes(0) }) it('onReload: updatedJob called with passed params', () => { const cbtService = new CrossBrowserTestingService({ ...testArgumens.beforeSession - }, {}) + } as any, {}) cbtService['_browser'] = browser const updateJobSpy = jest.spyOn(cbtService, 'updateJob') @@ -321,14 +307,14 @@ describe('wdio-crossbrowsertesting-service', () => { cbtService['_failures'] = 2 cbtService.onReload('oldSessionId', 'newSessionId') - expect(updateJobSpy).toBeCalledWith('oldSessionId', 2, true) + expect(updateJobSpy).toHaveBeenCalledWith('oldSessionId', 2, true) }) it('onReload with multi-remote: updatedJob called with passed params', () => { const cbtService = new CrossBrowserTestingService({ ...testArgumens.beforeSession - }, {}) + } as any, {}) cbtService['_browser'] = browser const updateJobSpy = jest.spyOn(cbtService, 'updateJob') @@ -337,21 +323,21 @@ describe('wdio-crossbrowsertesting-service', () => { cbtService['_failures'] = 2 cbtService.onReload('oldSessionId', 'sessionChromeA') - expect(updateJobSpy).toBeCalledWith('oldSessionId', 2, true, 'chromeA') + expect(updateJobSpy).toHaveBeenCalledWith('oldSessionId', 2, true, 'chromeA') }) it('getRestUrl', () => { - const cbtService = new CrossBrowserTestingService({}, {}) + const cbtService = new CrossBrowserTestingService({} as any, {} as any) cbtService['_browser'] = browser expect(cbtService.getRestUrl('testSessionId')) .toEqual('https://crossbrowsertesting.com/api/v3/selenium/testSessionId') }) it('getBody', () => { - const cbtService = new CrossBrowserTestingService({}, testArgumens.capabilities) + const cbtService = new CrossBrowserTestingService({} as any, testArgumens.capabilities as any) cbtService['_browser'] = browser - cbtService.beforeSuite({ title: 'Suite title' } as WebdriverIO.Suite) + cbtService.beforeSuite({ title: 'Suite title' } as Frameworks.Suite) expect(cbtService.getBody(0, false)).toEqual({ test: { @@ -377,12 +363,12 @@ describe('wdio-crossbrowsertesting-service', () => { }) it('getBody should contain browserName if passed', () => { - const cbtService = new CrossBrowserTestingService({}, { + const cbtService = new CrossBrowserTestingService({} as any, { name: 'Test suite', tags: ['tag3', 'tag4'], public: true, build: 344 - }) + } as any) expect(cbtService.getBody(0, false, 'internet explorer')).toEqual({ test: { @@ -396,7 +382,7 @@ describe('wdio-crossbrowsertesting-service', () => { }) it('updateJob success', async () => { - const service = new CrossBrowserTestingService({ user: 'test', key: 'testy' }, {}) + const service = new CrossBrowserTestingService({ user: 'test', key: 'testy' } as any, {}) service['_browser'] = browser service['_suiteTitle'] = 'my test' @@ -412,7 +398,7 @@ describe('wdio-crossbrowsertesting-service', () => { response.statusCode = 500; (got.put as jest.Mock).mockReturnValue(Promise.reject(response)) - const service = new CrossBrowserTestingService({ user: 'test', key: 'testy' }, {}) + const service = new CrossBrowserTestingService({ user: 'test', key: 'testy' } as any, {}) service['_browser'] = browser service['_suiteTitle'] = 'my test' const err: any = await service.updateJob('12345', 23, true).catch((err) => err) diff --git a/packages/wdio-crossbrowsertesting-service/tsconfig.json b/packages/wdio-crossbrowsertesting-service/tsconfig.json index 17ad4026314..d660189bdb9 100644 --- a/packages/wdio-crossbrowsertesting-service/tsconfig.json +++ b/packages/wdio-crossbrowsertesting-service/tsconfig.json @@ -3,10 +3,10 @@ "compilerOptions": { "baseUrl": ".", "outDir": "./build", - "rootDir": "./src", - "types": ["webdriverio", "@wdio/mocha-framework", "@wdio/jasmine-framework"] + "rootDir": "./src" }, "include": [ - "src/**/*" + "src/**/*", + "../../@types" ] } diff --git a/packages/wdio-crossbrowsertesting-service/tsconfig.prod.json b/packages/wdio-crossbrowsertesting-service/tsconfig.prod.json index e008f5e16a3..2a0e7e84140 100644 --- a/packages/wdio-crossbrowsertesting-service/tsconfig.prod.json +++ b/packages/wdio-crossbrowsertesting-service/tsconfig.prod.json @@ -3,10 +3,10 @@ "compilerOptions": { "baseUrl": ".", "outDir": "./build", - "rootDir": "./src", - "types": ["webdriverio", "@wdio/mocha-framework", "@wdio/jasmine-framework"] + "rootDir": "./src" }, "include": [ - "src/**/*" + "src/**/*", + "../../@types" ] } diff --git a/packages/wdio-cucumber-framework/cucumber-framework.d.ts b/packages/wdio-cucumber-framework/cucumber-framework.d.ts deleted file mode 100644 index af038121c3a..00000000000 --- a/packages/wdio-cucumber-framework/cucumber-framework.d.ts +++ /dev/null @@ -1,130 +0,0 @@ -/// - -import { SourceLocation, ScenarioResult, World } from "cucumber"; - -declare module "webdriverio" { - interface HookFunctions extends CucumberHookFunctions {} - interface Config extends CucumberOptsConfig {} -} - -declare module "@wdio/sync" { - interface HookFunctions extends CucumberHookFunctions {} - interface Config extends CucumberOptsConfig {} -} - -interface CucumberHookResult extends ScenarioResult { - exception?: Error -} -interface CucumberHookObject { - [key: string]: any; -} - -interface StepData { - uri: string, - feature: CucumberHookObject, - step: any -} - -interface CucumberHookFunctions { - beforeFeature?(uri: string, feature: CucumberHookObject, scenarios: CucumberHookObject[]): void; - beforeScenario?(uri: string, feature: CucumberHookObject, scenario: CucumberHookObject, sourceLocation: SourceLocation, context?: World): void; - beforeStep?(step: StepData, context: World): void; - afterStep?(step: StepData, context: World, result: { error?: any, result?: any, passed: boolean, duration: number }): void; - afterScenario?(uri: string, feature: CucumberHookObject, scenario: CucumberHookObject, result: CucumberHookResult, sourceLocation: SourceLocation, context?: World): void; - afterFeature?(uri: string, feature: CucumberHookObject, scenarios: CucumberHookObject[]): void; -} - -interface CucumberOptsConfig { - cucumberOpts?: CucumberOpts -} - -interface CucumberOpts { - /** - * Show full backtrace for errors. - * @default true - */ - backtrace?: boolean; - /** - * Require modules prior to requiring any support files. - * @default [] - * @example `['@babel/register']` or `[['@babel/register', { rootMode: 'upward', ignore: ['node_modules'] }]]` - */ - requireModule?: string[]; - /** - * Treat ambiguous definitions as errors. - * - * Please note that this is a @wdio/cucumber-framework specific option - * and not recognized by cucumber-js itself. - * @default false - */ - failAmbiguousDefinitions?: boolean; - /** - * Abort the run on first failure. - * @default false - */ - failFast?: boolean; - /** - * Treat undefined definitions as warnings. - * Please note that this is a @wdio/cucumber-framework specific option and - * not recognized by cucumber-js itself. - * @default false - */ - ignoreUndefinedDefinitions?: boolean; - /** - * Only execute the scenarios with name matching the expression (repeatable). - * @default [] - */ - name?: RegExp[]; - /** - * Specify the profile to use. - * @default [] - */ - profile?: string[]; - /** - * Require files containing your step definitions before executing features. - * You can also specify a glob to your step definitions. - * @default [] - * @example `[path.join(__dirname, 'step-definitions', 'my-steps.js')]` - */ - require?: string[]; - /** - * Specify a custom snippet syntax. - */ - snippetSyntax?: string; - /** - * Hide step definition snippets for pending steps. - * @default true - */ - snippets?: boolean; - /** - * Hide source uris. - * @default true - */ - source?: boolean; - /** - * Fail if there are any undefined or pending steps - * @default false - */ - strict?: boolean - /** - * Only execute the features or scenarios with tags matching the expression. - * Please see the [Cucumber documentation](https://docs.cucumber.io/cucumber/api/#tag-expressions) for more details. - */ - tagExpression?: string; - /** - * Add cucumber tags to feature or scenario name - * @default false - */ - tagsInTitle?: boolean; - /** - * Timeout in milliseconds for step definitions. - * @default 30000 - */ - timeout?: number; - /** - * Enable this to make webdriver.io behave as if scenarios - * and not steps were the tests. - * @default false - */ - scenarioLevelReporter?: boolean; -} diff --git a/packages/wdio-cucumber-framework/package.json b/packages/wdio-cucumber-framework/package.json index 73583d1b8ca..9b032eef5c0 100644 --- a/packages/wdio-cucumber-framework/package.json +++ b/packages/wdio-cucumber-framework/package.json @@ -27,13 +27,14 @@ "@cucumber/messages": "^13.2.1", "@types/is-glob": "^4.0.1", "@types/mockery": "^1.4.29", - "@wdio/config": "^6.10.11", "@wdio/logger": "6.10.10", + "@wdio/types": "^6.10.6", "@wdio/utils": "6.11.0", "cucumber": "^6.0.5", "expect-webdriverio": "^1.1.5", "glob": "^7.1.2", "is-glob": "^4.0.0", + "long": "^4.0.0", "mockery": "~2.1.0" }, "devDependencies": { @@ -45,5 +46,5 @@ "publishConfig": { "access": "public" }, - "types": "cucumber-framework.d.ts" + "types": "./build/index.d.ts" } diff --git a/packages/wdio-cucumber-framework/src/constants.ts b/packages/wdio-cucumber-framework/src/constants.ts index 3db74c8ca56..4b87cfc59f2 100644 --- a/packages/wdio-cucumber-framework/src/constants.ts +++ b/packages/wdio-cucumber-framework/src/constants.ts @@ -1,8 +1,8 @@ -import { CucumberOpts } from './types' +import { CucumberOptions } from './types' export const DEFAULT_TIMEOUT = 60000 -export const DEFAULT_OPTS: CucumberOpts = { +export const DEFAULT_OPTS: CucumberOptions = { backtrace: false, // show full backtrace for errors requireModule: [], // ("module") require MODULE files (repeatable) failAmbiguousDefinitions: false, // treat ambiguous definitions as errors diff --git a/packages/wdio-cucumber-framework/src/cucumberEventListener.ts b/packages/wdio-cucumber-framework/src/cucumberEventListener.ts index 3e5c795cef0..bd9e22b3ad3 100644 --- a/packages/wdio-cucumber-framework/src/cucumberEventListener.ts +++ b/packages/wdio-cucumber-framework/src/cucumberEventListener.ts @@ -2,6 +2,7 @@ import { EventEmitter } from 'events' import { Status, PickleFilter } from '@cucumber/cucumber' import { messages } from '@cucumber/messages' import logger from '@wdio/logger' +import type { Capabilities } from '@wdio/types' import { HookParams } from './types' import { filterPickles } from './utils' @@ -370,7 +371,7 @@ export default class CucumberEventListener extends EventEmitter { * returns a list of pickles to run based on capability tags * @param caps session capabilities */ - getPickleIds (caps: WebDriver.Capabilities) { + getPickleIds (caps: Capabilities.RemoteCapability) { const gherkinDocument = this._gherkinDocEvents[this._gherkinDocEvents.length - 1] return [...this._suiteMap.entries()] /** diff --git a/packages/wdio-cucumber-framework/src/index.ts b/packages/wdio-cucumber-framework/src/index.ts index cc51232b181..50d84119958 100644 --- a/packages/wdio-cucumber-framework/src/index.ts +++ b/packages/wdio-cucumber-framework/src/index.ts @@ -11,14 +11,15 @@ import EventDataCollector from '@cucumber/cucumber/lib/formatter/helpers/event_d import { ITestCaseHookParameter } from '@cucumber/cucumber/lib/support_code_library_builder/types' import { IRuntimeOptions } from '@cucumber/cucumber/lib/runtime' import { GherkinStreams } from '@cucumber/gherkin' +import { Long } from 'long' import { IdGenerator } from '@cucumber/messages' import { executeHooksWithArgs, testFnWrapper } from '@wdio/utils' -import type { ConfigOptions } from '@wdio/config' +import type { Capabilities, Options } from '@wdio/types' import CucumberReporter from './reporter' import { DEFAULT_OPTS } from './constants' -import { CucumberOpts, StepDefinitionOptions } from './types' +import { CucumberOptions, StepDefinitionOptions, HookFunctionExtension as HookFunctionExtensionImport } from './types' import { setUserHookNames } from './utils' const { incrementing } = IdGenerator @@ -26,7 +27,7 @@ const { incrementing } = IdGenerator class CucumberAdapter { private _cwd = process.cwd() private _newId = incrementing() - private _cucumberOpts: CucumberOpts + private _cucumberOpts: CucumberOptions private _hasTests: boolean private _cucumberFeaturesWithLineNumbers: string[] private _eventBroadcaster: EventEmitter @@ -36,11 +37,17 @@ class CucumberAdapter { getHookParams?: Function + /** + * make sure TS loads `@types/long` otherwise it won't find it in `@cucumber/messages` + * see also https://github.com/cucumber/cucumber-js/issues/1491 + */ + never?: Long + constructor( private _cid: string, - private _config: ConfigOptions, + private _config: Options.Testrunner, private _specs: string[], - private _capabilities: WebDriver.Capabilities, + private _capabilities: Capabilities.RemoteCapability, private _reporter: EventEmitter ) { this._cucumberOpts = Object.assign({}, DEFAULT_OPTS, this._config.cucumberOpts) @@ -230,7 +237,7 @@ class CucumberAdapter { * set `beforeScenario`, `afterScenario`, `beforeFeature`, `afterFeature` * @param {object} config config */ - addWdioHooks (config: ConfigOptions) { + addWdioHooks (config: Options.Testrunner) { const eventListener = this._cucumberReporter?.eventListener Cucumber.BeforeAll(async function wdioHookBeforeFeature() { const params = eventListener?.getHookParams() @@ -268,7 +275,7 @@ class CucumberAdapter { * wraps step definition code with sync/async runner with a retry option * @param {object} config */ - wrapSteps (config: ConfigOptions) { + wrapSteps (config: Options.Testrunner) { const wrapStep = this.wrapStep const cid = this._cid const getHookParams = () => this.getHookParams && this.getHookParams() @@ -305,7 +312,7 @@ class CucumberAdapter { wrapStep( code: Function, isStep: boolean, - config: ConfigOptions, + config: Options.Testrunner, cid: string, options: StepDefinitionOptions, getHookParams: Function @@ -346,3 +353,10 @@ adapterFactory.init = async function (...args: any[]) { export default adapterFactory export { CucumberAdapter, adapterFactory } + +declare global { + namespace WebdriverIO { + interface CucumberOpts extends CucumberOptions {} + interface HookFunctionExtension extends HookFunctionExtensionImport {} + } +} diff --git a/packages/wdio-cucumber-framework/src/types.ts b/packages/wdio-cucumber-framework/src/types.ts index 40ed95d1f1d..378f24e4ee5 100644 --- a/packages/wdio-cucumber-framework/src/types.ts +++ b/packages/wdio-cucumber-framework/src/types.ts @@ -1,6 +1,8 @@ +import type { Capabilities } from '@wdio/types' import type { messages } from '@cucumber/messages' +import type { ITestCaseHookParameter } from '@cucumber/cucumber/lib/support_code_library_builder/types' -export interface CucumberOpts { +export interface CucumberOptions { /** * Show full backtrace for errors. * @default true @@ -99,7 +101,7 @@ export interface CucumberOpts { } export interface ReporterOptions { - capabilities: WebDriver.Capabilities + capabilities: Capabilities.RemoteCapability ignoreUndefinedDefinitions: boolean failAmbiguousDefinitions: boolean tagsInTitle: boolean @@ -123,3 +125,53 @@ export interface HookParams { export interface StepDefinitionOptions { retry: number } + +export interface HookFunctionExtension { + /** + * + * Runs before a Cucumber Feature. + * @param uri path to feature file + * @param feature Cucumber feature object + */ + beforeFeature?(uri: string, feature: messages.GherkinDocument.IFeature): void; + + /** + * + * Runs before a Cucumber Scenario. + * @param world world object containing information on pickle and test step + */ + beforeScenario?(world: ITestCaseHookParameter): void; + + /** + * + * Runs before a Cucumber Step. + * @param step step data + * @param context Cucumber world + */ + beforeStep?(step: messages.Pickle.IPickleStep, context: unknown): void; + + /** + * + * Runs after a Cucumber Step. + * @param step step data + * @param context Cucumber world + * @param result step result + */ + afterStep?(step: messages.Pickle.IPickleStep, context: unknown): void; + + /** + * + * Runs before a Cucumber Scenario. + * @param world world object containing information on pickle and test step + */ + afterScenario?(world: ITestCaseHookParameter): void; + + /** + * + * Runs after a Cucumber Feature. + * @param uri path to feature file + * @param feature Cucumber feature object + * @param scenario Cucumber scenario object + */ + afterFeature?(uri: string, feature: messages.GherkinDocument.IFeature): void; +} diff --git a/packages/wdio-cucumber-framework/src/utils.ts b/packages/wdio-cucumber-framework/src/utils.ts index 5261bcfd0e7..b855e496434 100644 --- a/packages/wdio-cucumber-framework/src/utils.ts +++ b/packages/wdio-cucumber-framework/src/utils.ts @@ -6,6 +6,7 @@ import logger from '@wdio/logger' import * as Cucumber from '@cucumber/cucumber' import { supportCodeLibraryBuilder } from '@cucumber/cucumber' import { messages } from '@cucumber/messages' +import { Capabilities } from '@wdio/types' import { CUCUMBER_HOOK_DEFINITION_TYPES } from './constants' import { TestHookDefinitionConfig } from './types' @@ -134,7 +135,7 @@ export function setUserHookNames (options: typeof supportCodeLibraryBuilder) { * For example "@skip(browserName=firefox)" or "@skip(browserName=chrome,platform=/.+n?x/)" * @param {*} testCase */ -export function filterPickles (capabilities: WebDriver.Capabilities, pickle?: messages.IPickle) { +export function filterPickles (capabilities: Capabilities.RemoteCapability, pickle?: messages.IPickle) { const skipTag = /^@skip\((.*)\)$/ const match = (value: string, expr: RegExp) => { @@ -163,6 +164,6 @@ export function filterPickles (capabilities: WebDriver.Capabilities, pickle?: me .map(p => p.name?.match(skipTag)) .filter(Boolean) .map(m => parse(m![1])) - .find((filter: WebDriver.Capabilities) => Object.keys(filter) - .every((key: keyof WebDriver.Capabilities) => match(capabilities[key] as string, filter[key] as RegExp)))) + .find((filter: Capabilities.Capabilities) => Object.keys(filter) + .every((key: keyof Capabilities.Capabilities) => match((capabilities as any)[key], filter[key] as RegExp)))) } diff --git a/packages/wdio-cucumber-framework/tsconfig.json b/packages/wdio-cucumber-framework/tsconfig.json index a32652092d3..d660189bdb9 100644 --- a/packages/wdio-cucumber-framework/tsconfig.json +++ b/packages/wdio-cucumber-framework/tsconfig.json @@ -6,6 +6,7 @@ "rootDir": "./src" }, "include": [ - "src/**/*" + "src/**/*", + "../../@types" ] } diff --git a/packages/wdio-cucumber-framework/tsconfig.prod.json b/packages/wdio-cucumber-framework/tsconfig.prod.json index c7e2194a8b4..2a0e7e84140 100644 --- a/packages/wdio-cucumber-framework/tsconfig.prod.json +++ b/packages/wdio-cucumber-framework/tsconfig.prod.json @@ -6,6 +6,7 @@ "rootDir": "./src" }, "include": [ - "src/**/*" + "src/**/*", + "../../@types" ] } diff --git a/packages/wdio-devtools-service/devtools-service.d.ts b/packages/wdio-devtools-service/devtools-service.d.ts deleted file mode 100644 index 2f76dc6868f..00000000000 --- a/packages/wdio-devtools-service/devtools-service.d.ts +++ /dev/null @@ -1,219 +0,0 @@ -declare module WebdriverIO { - interface ServiceOption extends DevtoolsConfig { } - interface Browser extends DevtoolsBrowser { } -} - -type PWAAudits = 'isInstallable' | 'serviceWorker' | 'splashScreen' | 'themedOmnibox' | 'contentWith' | 'viewport' | 'appleTouchIcon' | 'maskableIcon' -type NetworkStates = 'offline' | 'GPRS' | 'Regular 2G' | 'Good 2G' | 'Regular 3G' | 'Good 3G' | 'Regular 4G' | 'DSL' | 'Wifi' | 'online'; - -interface LHAuditResult { - score: number - warnings?: any[] - notApplicable?: boolean - numericValue?: number - numericUnit?: string - displayValue?: { - i18nId: string - values: any - formattedDefault: string - } - details?: any -} - -interface AuditResult { - passed: boolean - details: Record -} - -interface ErrorAudit { - score: 0 - error: Error -} - -interface Totals { - total: number; - covered: number; - skipped: number; - pct: number; -} - -interface CoverageSummaryData { - lines: Totals; - statements: Totals; - branches: Totals; - functions: Totals; -} - -interface Coverage { - lines: Totals - statements: Totals - functions: Totals - branches: Totals - files: Record -} - -interface Viewport { - /** - * page width in pixels - */ - width: number, - /** - * page height in pixels - */ - height: number, - /** - * Specify device scale factor (can be thought of as dpr). Defaults to 1 - */ - deviceScaleFactor?: number, - /** - * Whether the meta viewport tag is taken into account. Defaults to false - */ - isMobile?: boolean, - /** - * Specifies if viewport supports touch events. Defaults to false - */ - hasTouch?: boolean, - /** - * Specifies if viewport is in landscape mode. Defaults to false - */ - isLandscape?: boolean -} -interface CustomDevice { - viewport: Viewport, - userAgent: string -} -type DeviceProfiles = 'Blackberry PlayBook' | 'BlackBerry Z30' | 'Galaxy Note 3' | 'Galaxy Note II' | 'Galaxy S III' | 'Galaxy S5' | 'iPad' | 'iPad Mini' | 'iPad Pro' | 'iPhone 4' | 'iPhone 5' | 'iPhone 6' | 'iPhone 6 Plus' | 'iPhone 7' | 'iPhone 7 Plus' | 'iPhone 8' | 'iPhone 8 Plus' | 'iPhone SE' | 'iPhone X' | 'JioPhone 2' | 'Kindle Fire HDX' | 'LG Optimus L70' | 'Microsoft Lumia 550' | 'Microsoft Lumia 950' | 'Nexus 10' | 'Nexus 4' | 'Nexus 5' | 'Nexus 5X' | 'Nexus 6' | 'Nexus 6P' | 'Nexus 7' | 'Nokia Lumia 520' | 'Nokia N9' | 'Pixel 2' | 'Pixel 2 XL' | CustomDevice - -interface DevtoolsConfig { - /** - * options for the code coverage reporter - */ - coverageReporter?: CoverageReporterOptions; -} - -interface CoverageReporterOptions { - /** - * whether or not to enable code coverage reporting - * @default false - */ - enable?: boolean - /** - * Directory where JS coverage reports are stored - */ - logDir?: string - /** - * format of report - * @default json - */ - type?: 'none' | 'clover' | 'cobertura' | 'html-spa' | 'html' | 'json' | 'json-summary' | 'lcov' | 'lcovonly' | 'teamcity' | 'text' | 'text-lcov' | 'text-summary' - /** - * Options for coverage report, for details see https://github.com/DefinitelyTyped/DefinitelyTyped/blob/master/types/istanbul-lib-report/index.d.ts - */ - options?: any -} - -interface PerformanceAuditOptions { - /** - * Network throttling artificially limits the maximum download throughput (rate of data transfer). (e.g. Fast 3G). - */ - networkThrottling?: NetworkStates, - /** - * Define CPU throttling to understand how your page performs under that constraint (e.g. 1.5). - */ - cpuThrottling?: number, - /** - * Enable or disable cache of resources. Defaults to true. - */ - cacheEnabled?: boolean -} - -interface DevtoolsBrowser { - /** - * Enables auto performance audits for all page loads that are cause by calling the url command or clicking on a link or anything that causes a page load. - * You can pass in a config object to determine some throttling options. The default throttling profile is Good 3G network with a 4x CPU trottling. - */ - enablePerformanceAudits(params?: PerformanceAuditOptions): void; - /** - * Disable the performance audits - */ - disablePerformanceAudits(): void; - /** - * Get most common used performance metrics - */ - getMetrics(): object; - /** - * Get some useful diagnostics about the page load - */ - getDiagnostics(): object; - /** - * Returns a list with a breakdown of all main thread task and their total duration - */ - getMainThreadWorkBreakdown(): object[]; - /** - * Returns the Lighthouse Performance Score which is a weighted mean of the following metrics: firstMeaningfulPaint, firstCPUIdle, firstInteractive, speedIndex, estimatedInputLatency - */ - getPerformanceScore(): number; - - /** - * The service allows you to emulate a specific device type. - * If set, the browser viewport will be modified to fit the device capabilities as well as the user agent will set according to the device user agent. - * Note: This only works if you don't use mobileEmulation within capabilities['goog:chromeOptions']. If mobileEmulation is present the call to browser.emulateDevice() won't do anything. - */ - emulateDevice(deviceProfile: DeviceProfiles): void; - - /** - * Runs various PWA Lighthouse audits on the current opened page. - * Read more about Lighthouse PWA audits at https://web.dev/lighthouse-pwa/. - */ - checkPWA(auditsToBeRun?: PWAAudits[]): AuditResult; - - /** - * Returns the coverage report for the current opened page. - */ - getCoverageReport(): Coverage; - - /** - * The cdp command is a custom command added to the browser scope that allows you to call directly commands to the protocol. - */ - cdp( - domain: string, - command: string, - arguments?: object - ): any; - /** - * Returns the host and port the Chrome DevTools interface is connected to. - */ - cdpConnection(): { host: string, port: number }; - /** - * Helper method to get the nodeId of an element in the page. - * NodeIds are similar like WebDriver node ids an identifier for a node. - * It can be used as a parameter for other Chrome DevTools methods, e.g. DOM.focus. - */ - getNodeId(selector: string): number; - /** - * Helper method to get the nodeId of an element in the page. - * NodeIds are similar like WebDriver node ids an identifier for a node. - * It can be used as a parameter for other Chrome DevTools methods, e.g. DOM.focus. - */ - getNodeIds(selector: string): number[]; - /** - * Start tracing the browser. You can optionally pass in custom tracing categories and the sampling frequency. - */ - startTracing( - categories?: string, - samplingFrequency?: number - ): void; - /** - * Stop tracing the browser. - */ - endTracing(): void; - /** - * Returns the tracelogs that was captured within the tracing period. - * You can use this command to store the trace logs on the file system to analyse the trace via Chrome DevTools interface. - */ - getTraceLogs(): object; - /** - * Returns page weight information of the last page load. - */ - getPageWeight(): object; -} diff --git a/packages/wdio-devtools-service/package.json b/packages/wdio-devtools-service/package.json index 9a3ac75a8ad..3b12d90442a 100644 --- a/packages/wdio-devtools-service/package.json +++ b/packages/wdio-devtools-service/package.json @@ -30,6 +30,7 @@ "@tracerbench/trace-event": "^4.5.2", "@types/puppeteer-core": "^2.0.0", "@wdio/logger": "6.10.10", + "@wdio/types": "^6.10.6", "atob": "^2.1.2", "babel-plugin-istanbul": "^6.0.0", "core-js": "~3.8.2", @@ -40,7 +41,8 @@ "lighthouse": "^7.0.0", "puppeteer-core": "^5.1.0", "speedline": "^1.4.1", - "stable": "^0.1.8" + "stable": "^0.1.8", + "webdriverio": "^6.11.3" }, "devDependencies": { "@types/atob": "^2.1.2" @@ -51,5 +53,5 @@ "publishConfig": { "access": "public" }, - "types": "devtools-service.d.ts" + "types": "./build/index.d.ts" } diff --git a/packages/wdio-devtools-service/src/auditor.ts b/packages/wdio-devtools-service/src/auditor.ts index 7f11c6a7eed..e0dbd55324a 100644 --- a/packages/wdio-devtools-service/src/auditor.ts +++ b/packages/wdio-devtools-service/src/auditor.ts @@ -12,6 +12,7 @@ import TotalBlockingTime from 'lighthouse/lighthouse-core/audits/metrics/total-b import ReportScoring from 'lighthouse/lighthouse-core/scoring' import defaultConfig from 'lighthouse/lighthouse-core/config/default-config' import logger from '@wdio/logger' +import type { Browser, CustomInstanceCommands } from 'webdriverio' import { DEFAULT_FORM_FACTOR, PWA_AUDITS } from './constants' import type { @@ -72,7 +73,7 @@ export default class Auditor { * an Auditor instance is created for every trace so provide an updateCommands * function to receive the latest performance metrics with the browser instance */ - updateCommands (browser: WebdriverIO.BrowserObject, customFn?: WebdriverIO.AddCommandFn) { + updateCommands (browser: Browser<'async'>, customFn?: CustomInstanceCommands>['addCommand']) { const commands = Object.getOwnPropertyNames(Object.getPrototypeOf(this)).filter( fnName => fnName !== 'constructor' && fnName !== 'updateCommands' && !fnName.startsWith('_')) commands.forEach(fnName => browser.addCommand(fnName, customFn || (this[fnName as keyof Auditor] as any).bind(this))) diff --git a/packages/wdio-devtools-service/src/commands.ts b/packages/wdio-devtools-service/src/commands.ts index fb0f6edc27c..2025fe19707 100644 --- a/packages/wdio-devtools-service/src/commands.ts +++ b/packages/wdio-devtools-service/src/commands.ts @@ -1,5 +1,6 @@ import 'core-js/modules/web.url' import logger from '@wdio/logger' +import type { Browser, MultiRemoteBrowser } from 'webdriverio' import type { TraceEvent } from '@tracerbench/trace-event' import type { CDPSession } from 'puppeteer-core/lib/cjs/puppeteer/common/Connection' @@ -21,7 +22,7 @@ export default class CommandHandler { constructor ( private _session: CDPSession, private _page: Page, - browser: WebdriverIO.BrowserObject | WebdriverIO.MultiRemoteBrowserObject + browser: Browser<'async'> | MultiRemoteBrowser<'async'> ) { this._session = _session this._page = _page diff --git a/packages/wdio-devtools-service/src/constants.ts b/packages/wdio-devtools-service/src/constants.ts index e6ddbd102c3..88fe097eab8 100644 --- a/packages/wdio-devtools-service/src/constants.ts +++ b/packages/wdio-devtools-service/src/constants.ts @@ -53,7 +53,7 @@ export const IGNORED_URLS = [ 'data:,', // empty pages 'about:', // new tabs 'chrome-extension://' // all chrome extensions -] +] as const export const FRAME_LOAD_START_TIMEOUT = 2000 export const TRACING_TIMEOUT = 15000 diff --git a/packages/wdio-devtools-service/src/index.ts b/packages/wdio-devtools-service/src/index.ts index 9bb6a48fc9e..ef39ab95bae 100644 --- a/packages/wdio-devtools-service/src/index.ts +++ b/packages/wdio-devtools-service/src/index.ts @@ -1,9 +1,11 @@ import logger from '@wdio/logger' import puppeteerCore from 'puppeteer-core' +import type { Browser, MultiRemoteBrowser } from 'webdriverio' +import type { Capabilities, Services, FunctionProperties, ThenArg } from '@wdio/types' import type { Page } from 'puppeteer-core/lib/cjs/puppeteer/common/Page' import type { CDPSession } from 'puppeteer-core/lib/cjs/puppeteer/common/Connection' -import type { Browser } from 'puppeteer-core/lib/cjs/puppeteer/common/Browser' +import type { Browser as PuppeteerBrowser } from 'puppeteer-core/lib/cjs/puppeteer/common/Browser' import type { Target } from 'puppeteer-core/lib/cjs/puppeteer/common/Target' import CommandHandler from './commands' @@ -19,11 +21,11 @@ import { DevtoolsConfig, FormFactor, EnablePerformanceAuditsOptions, DeviceDescr const log = logger('@wdio/devtools-service') const TRACE_COMMANDS = ['click', 'navigateTo', 'url'] -export default class DevToolsService implements WebdriverIO.ServiceInstance { +export default class DevToolsService implements Services.ServiceInstance { private _isSupported = false private _shouldRunPerformanceAudits = false - private _puppeteer?: Browser + private _puppeteer?: PuppeteerBrowser private _target?: Target private _page: Page | null = null private _session?: CDPSession @@ -36,11 +38,12 @@ export default class DevToolsService implements WebdriverIO.ServiceInstance { private _traceGatherer?: TraceGatherer private _devtoolsGatherer?: DevtoolsGatherer private _coverageGatherer?: CoverageGatherer - private _browser?: WebdriverIO.BrowserObject | WebdriverIO.MultiRemoteBrowserObject + private _pwaGatherer?: PWAGatherer + private _browser?: Browser<'async'> | MultiRemoteBrowser<'async'> constructor (private _options: DevtoolsConfig) {} - beforeSession (_: WebdriverIO.Config, caps: WebDriver.DesiredCapabilities) { + beforeSession (_: unknown, caps: Capabilities.Capabilities) { if (!isBrowserSupported(caps)) { return log.error(UNSUPPORTED_ERROR_MESSAGE) } @@ -48,9 +51,9 @@ export default class DevToolsService implements WebdriverIO.ServiceInstance { } before ( - caps: WebDriver.Capabilities, + caps: Capabilities.RemoteCapability, specs: string[], - browser: WebdriverIO.BrowserObject | WebdriverIO.MultiRemoteBrowserObject + browser: Browser<'async'> | MultiRemoteBrowser<'async'> ) { this._browser = browser this._isSupported = this._isSupported || Boolean(this._browser.puppeteer) @@ -63,7 +66,7 @@ export default class DevToolsService implements WebdriverIO.ServiceInstance { } // resetting puppeteer on sessionReload, so a new puppeteer session will be attached - this._browser.puppeteer = null + delete this._browser.puppeteer return this._setupHandler() } @@ -93,12 +96,12 @@ export default class DevToolsService implements WebdriverIO.ServiceInstance { */ this._traceGatherer.once('tracingComplete', (traceEvents) => { const auditor = new Auditor(traceEvents, this._devtoolsGatherer?.getLogs(), this._formFactor) - auditor.updateCommands(this._browser as WebdriverIO.BrowserObject) + auditor.updateCommands(this._browser as Browser<'async'>) }) this._traceGatherer.once('tracingError', (err: Error) => { const auditor = new Auditor() - auditor.updateCommands(this._browser as WebdriverIO.BrowserObject, /* istanbul ignore next */() => { + auditor.updateCommands(this._browser as Browser<'async'>, /* istanbul ignore next */() => { throw new Error(`Couldn't capture performance due to: ${err.message}`) }) }) @@ -192,15 +195,25 @@ export default class DevToolsService implements WebdriverIO.ServiceInstance { await this._session.send('Network.emulateNetworkConditions', NETWORK_STATES[networkThrottling]) } + async _checkPWA (auditsToBeRun: PWAAudits[] = []) { + const auditor = new Auditor() + const artifacts = await this._pwaGatherer!.gatherData() + return auditor._auditPWA(artifacts, auditsToBeRun) + } + + _getCoverageReport () { + return this._coverageGatherer!.getCoverageReport() + } + async _setupHandler () { if (!this._isSupported || !this._browser) { - return setUnsupportedCommand(this._browser as WebdriverIO.BrowserObject) + return setUnsupportedCommand(this._browser as Browser<'async'>) } /** * casting is required as types differ between core and definitely typed types */ - this._puppeteer = await this._browser.getPuppeteer() as any as Browser + this._puppeteer = await (this._browser as Browser<'async'>).getPuppeteer() /* istanbul ignore next */ if (!this._puppeteer) { @@ -245,7 +258,7 @@ export default class DevToolsService implements WebdriverIO.ServiceInstance { */ if (this._options.coverageReporter?.enable) { this._coverageGatherer = new CoverageGatherer(this._page, this._options.coverageReporter) - this._browser.addCommand('getCoverageReport', this._coverageGatherer.getCoverageReport.bind(this._coverageGatherer)) + this._browser.addCommand('getCoverageReport', this._getCoverageReport.bind(this)) await this._coverageGatherer.init() } @@ -265,11 +278,45 @@ export default class DevToolsService implements WebdriverIO.ServiceInstance { this._browser.addCommand('disablePerformanceAudits', this._disablePerformanceAudits.bind(this)) this._browser.addCommand('emulateDevice', this._emulateDevice.bind(this)) - const pwaGatherer = new PWAGatherer(this._session, this._page) - this._browser.addCommand('checkPWA', async (auditsToBeRun: PWAAudits[]) => { - const auditor = new Auditor() - const artifacts = await pwaGatherer.gatherData() - return auditor._auditPWA(artifacts, auditsToBeRun) - }) + this._pwaGatherer = new PWAGatherer(this._session, this._page) + this._browser.addCommand('checkPWA', this._checkPWA.bind(this)) + } +} + +export * from './types' + +type ServiceCommands = Omit, keyof Services.HookFunctions | '_setupHandler'> +type CommandHandlerCommands = FunctionProperties +type AuditorCommands = Omit, '_audit' | '_auditPWA' | 'updateCommands'> + +/** + * ToDo(Christian): use key remapping with TS 4.1 + * https://www.typescriptlang.org/docs/handbook/release-notes/typescript-4-1.html#key-remapping-in-mapped-types + */ +interface BrowserExtension extends CommandHandlerCommands, AuditorCommands { + enablePerformanceAudits: ServiceCommands['_enablePerformanceAudits'] + disablePerformanceAudits: ServiceCommands['_disablePerformanceAudits'] + emulateDevice: ServiceCommands['_emulateDevice'] + setThrottlingProfile: ServiceCommands['_setThrottlingProfile'] + checkPWA: ServiceCommands['_checkPWA'] + getCoverageReport: ServiceCommands['_getCoverageReport'] +} +export type BrowserExtensionSync = { + [K in keyof BrowserExtension]: (...args: Parameters) => ThenArg> +} + +declare global { + namespace WebdriverIO { + interface ServiceOption extends DevtoolsConfig {} + } + + namespace WebdriverIOAsync { + interface Browser extends BrowserExtension { } + interface MultiRemoteBrowser extends BrowserExtension { } + } + + namespace WebdriverIOSync { + interface Browser extends BrowserExtensionSync { } + interface MultiRemoteBrowser extends BrowserExtensionSync { } } } diff --git a/packages/wdio-devtools-service/src/types.ts b/packages/wdio-devtools-service/src/types.ts index 4ef5ba7f0ca..5530702b97f 100644 --- a/packages/wdio-devtools-service/src/types.ts +++ b/packages/wdio-devtools-service/src/types.ts @@ -142,6 +142,7 @@ export interface ErrorAudit { } export type PWAAudits = keyof typeof PWA_AUDITS +export type NetworkStates = 'offline' | 'GPRS' | 'Regular 2G' | 'Good 2G' | 'Regular 3G' | 'Good 3G' | 'Regular 4G' | 'DSL' | 'Wifi' | 'online'; export interface Coverage { lines: Totals @@ -150,3 +151,116 @@ export interface Coverage { branches: Totals files: Record } + +export interface CustomDevice { + viewport: Viewport, + userAgent: string +} + +export type DeviceProfiles = 'Blackberry PlayBook' | 'BlackBerry Z30' | 'Galaxy Note 3' | 'Galaxy Note II' | 'Galaxy S III' | 'Galaxy S5' | 'iPad' | 'iPad Mini' | 'iPad Pro' | 'iPhone 4' | 'iPhone 5' | 'iPhone 6' | 'iPhone 6 Plus' | 'iPhone 7' | 'iPhone 7 Plus' | 'iPhone 8' | 'iPhone 8 Plus' | 'iPhone SE' | 'iPhone X' | 'JioPhone 2' | 'Kindle Fire HDX' | 'LG Optimus L70' | 'Microsoft Lumia 550' | 'Microsoft Lumia 950' | 'Nexus 10' | 'Nexus 4' | 'Nexus 5' | 'Nexus 5X' | 'Nexus 6' | 'Nexus 6P' | 'Nexus 7' | 'Nokia Lumia 520' | 'Nokia N9' | 'Pixel 2' | 'Pixel 2 XL' | CustomDevice + +export interface PerformanceAuditOptions { + /** + * Network throttling artificially limits the maximum download throughput (rate of data transfer). (e.g. Fast 3G). + */ + networkThrottling?: NetworkStates, + /** + * Define CPU throttling to understand how your page performs under that constraint (e.g. 1.5). + */ + cpuThrottling?: number, + /** + * Enable or disable cache of resources. Defaults to true. + */ + cacheEnabled?: boolean +} + +export interface DevtoolsBrowser { + /** + * Enables auto performance audits for all page loads that are cause by calling the url command or clicking on a link or anything that causes a page load. + * You can pass in a config object to determine some throttling options. The default throttling profile is Good 3G network with a 4x CPU trottling. + */ + enablePerformanceAudits(params?: PerformanceAuditOptions): void; + /** + * Disable the performance audits + */ + disablePerformanceAudits(): void; + /** + * Get most common used performance metrics + */ + getMetrics(): object; + /** + * Get some useful diagnostics about the page load + */ + getDiagnostics(): object; + /** + * Returns a list with a breakdown of all main thread task and their total duration + */ + getMainThreadWorkBreakdown(): object[]; + /** + * Returns the Lighthouse Performance Score which is a weighted mean of the following metrics: firstMeaningfulPaint, firstCPUIdle, firstInteractive, speedIndex, estimatedInputLatency + */ + getPerformanceScore(): number; + + /** + * The service allows you to emulate a specific device type. + * If set, the browser viewport will be modified to fit the device capabilities as well as the user agent will set according to the device user agent. + * Note: This only works if you don't use mobileEmulation within capabilities['goog:chromeOptions']. If mobileEmulation is present the call to browser.emulateDevice() won't do anything. + */ + emulateDevice(deviceProfile: DeviceProfiles): void; + + /** + * Runs various PWA Lighthouse audits on the current opened page. + * Read more about Lighthouse PWA audits at https://web.dev/lighthouse-pwa/. + */ + checkPWA(auditsToBeRun?: PWAAudits[]): AuditResult; + + /** + * Returns the coverage report for the current opened page. + */ + getCoverageReport(): Coverage; + + /** + * The cdp command is a custom command added to the browser scope that allows you to call directly commands to the protocol. + */ + cdp( + domain: string, + command: string, + args?: object + ): any; + /** + * Returns the host and port the Chrome DevTools interface is connected to. + */ + cdpConnection(): { host: string, port: number }; + /** + * Helper method to get the nodeId of an element in the page. + * NodeIds are similar like WebDriver node ids an identifier for a node. + * It can be used as a parameter for other Chrome DevTools methods, e.g. DOM.focus. + */ + getNodeId(selector: string): number; + /** + * Helper method to get the nodeId of an element in the page. + * NodeIds are similar like WebDriver node ids an identifier for a node. + * It can be used as a parameter for other Chrome DevTools methods, e.g. DOM.focus. + */ + getNodeIds(selector: string): number[]; + /** + * Start tracing the browser. You can optionally pass in custom tracing categories and the sampling frequency. + */ + startTracing( + categories?: string, + samplingFrequency?: number + ): void; + /** + * Stop tracing the browser. + */ + endTracing(): void; + /** + * Returns the tracelogs that was captured within the tracing period. + * You can use this command to store the trace logs on the file system to analyse the trace via Chrome DevTools interface. + */ + getTraceLogs(): object; + /** + * Returns page weight information of the last page load. + */ + getPageWeight(): object; +} diff --git a/packages/wdio-devtools-service/src/utils.ts b/packages/wdio-devtools-service/src/utils.ts index 69c8dffe0bc..2719254127d 100644 --- a/packages/wdio-devtools-service/src/utils.ts +++ b/packages/wdio-devtools-service/src/utils.ts @@ -1,3 +1,6 @@ +import type { Browser, MultiRemoteBrowser } from 'webdriverio' +import type { Capabilities } from '@wdio/types' + import { IGNORED_URLS, UNSUPPORTED_ERROR_MESSAGE } from './constants' import { RequestPayload } from './handler/network' @@ -9,7 +12,7 @@ const SUPPORTED_BROWSERS_AND_MIN_VERSIONS = { 'google chrome': 63 } -export function setUnsupportedCommand (browser: WebdriverIO.BrowserObject | WebdriverIO.MultiRemoteBrowserObject) { +export function setUnsupportedCommand (browser: Browser<'async'> | MultiRemoteBrowser<'async'>) { return browser.addCommand('cdp', /* istanbul ignore next */() => { throw new Error(UNSUPPORTED_ERROR_MESSAGE) }) @@ -38,9 +41,9 @@ export function isSupportedUrl (url: string) { * @param {object} caps capabilities * @param {number} minVersion minimal chrome browser version */ -export function isBrowserVersionLower (caps: WebDriver.Capabilities, minVersion: number) { +export function isBrowserVersionLower (caps: Capabilities.Capabilities, minVersion: number) { const versionProp = VERSION_PROPS.find( - (prop: keyof WebDriver.Capabilities) => caps[prop] + (prop: keyof Capabilities.Capabilities) => caps[prop] ) as 'browserVersion' const browserVersion = getBrowserMajorVersion(caps[versionProp]) return typeof browserVersion === 'number' && browserVersion < minVersion @@ -63,7 +66,7 @@ export function getBrowserMajorVersion (version?: string | number) { * check if browser is supported based on caps.browserName and caps.version * @param {object} caps capabilities */ -export function isBrowserSupported(caps: WebDriver.Capabilities) { +export function isBrowserSupported(caps: Capabilities.Capabilities) { if ( !caps.browserName || !(caps.browserName.toLowerCase() in SUPPORTED_BROWSERS_AND_MIN_VERSIONS) || diff --git a/packages/wdio-devtools-service/tests/service.test.ts b/packages/wdio-devtools-service/tests/service.test.ts index 630c976d6a6..49065a87d84 100644 --- a/packages/wdio-devtools-service/tests/service.test.ts +++ b/packages/wdio-devtools-service/tests/service.test.ts @@ -382,7 +382,7 @@ test('onReload hook', async () => { ;(service['_browser'] as any).puppeteer = 'suppose to be reset after reload' as any service.onReload() expect(service._setupHandler).toBeCalledTimes(1) - expect((service['_browser'] as any).puppeteer).toBeNull() + expect((service['_browser'] as any).puppeteer).toBeUndefined() }) test('after hook', async () => { diff --git a/packages/wdio-devtools-service/tests/utils.test.ts b/packages/wdio-devtools-service/tests/utils.test.ts index 5257b7b3f36..2cddc3897c4 100644 --- a/packages/wdio-devtools-service/tests/utils.test.ts +++ b/packages/wdio-devtools-service/tests/utils.test.ts @@ -1,9 +1,13 @@ +import type { Browser } from 'webdriverio' + import { sumByKey, isBrowserVersionLower, getBrowserMajorVersion, isBrowserSupported, setUnsupportedCommand } from '../src/utils' import { RequestPayload } from '../src/handler/network' +const expect = global.expect as any as jest.Expect + jest.mock('fs', () => ({ readFileSync: jest.fn().mockReturnValue('1234\nsomepath'), existsSync: jest.fn() @@ -21,8 +25,8 @@ test('sumByKey', () => { test('setUnsupportedCommand', () => { const browser = { addCommand: jest.fn() } - setUnsupportedCommand(browser as unknown as WebdriverIO.BrowserObject) - expect(browser.addCommand).toBeCalledWith('cdp', expect.any(Function)) + setUnsupportedCommand(browser as unknown as Browser) + expect(browser.addCommand).toHaveBeenCalledWith('cdp', expect.any(Function)) const fn = browser.addCommand.mock.calls[0][1] expect(fn).toThrow() }) @@ -33,6 +37,7 @@ describe('isBrowserVersionLower', () => { }) test('should return true if version is lower than required', () => { + // @ts-expect-error invalid param expect(isBrowserVersionLower({ version: 62 }, 63)).toBe(true) }) @@ -85,6 +90,7 @@ describe('isBrowserSupported', () => { test('should return true when the browserName is not specified', () => { const capsEmpty = { version: 83 } + // @ts-expect-error invalid param expect(isBrowserSupported(capsEmpty)).toEqual(false) }) test('should return true when the version number is not specified', () => { diff --git a/packages/wdio-devtools-service/tsconfig.json b/packages/wdio-devtools-service/tsconfig.json index f5bc7a0385c..d660189bdb9 100644 --- a/packages/wdio-devtools-service/tsconfig.json +++ b/packages/wdio-devtools-service/tsconfig.json @@ -3,10 +3,10 @@ "compilerOptions": { "baseUrl": ".", "outDir": "./build", - "rootDir": "./src", - "types": ["webdriverio", "@wdio/logger"] + "rootDir": "./src" }, "include": [ - "src/**/*" + "src/**/*", + "../../@types" ] } diff --git a/packages/wdio-devtools-service/tsconfig.prod.json b/packages/wdio-devtools-service/tsconfig.prod.json index 8f60cbb6a61..2a0e7e84140 100644 --- a/packages/wdio-devtools-service/tsconfig.prod.json +++ b/packages/wdio-devtools-service/tsconfig.prod.json @@ -3,10 +3,10 @@ "compilerOptions": { "baseUrl": ".", "outDir": "./build", - "rootDir": "./src", - "types": ["webdriverio", "@wdio/logger"] + "rootDir": "./src" }, "include": [ - "src/**/*" + "src/**/*", + "../../@types" ] } diff --git a/packages/wdio-dot-reporter/package.json b/packages/wdio-dot-reporter/package.json index a671d8422ba..359b4c6f9b8 100644 --- a/packages/wdio-dot-reporter/package.json +++ b/packages/wdio-dot-reporter/package.json @@ -23,6 +23,7 @@ }, "dependencies": { "@wdio/reporter": "6.11.0", + "@wdio/types": "^6.10.6", "chalk": "^4.0.0" }, "devDependencies": { @@ -34,5 +35,6 @@ }, "publishConfig": { "access": "public" - } + }, + "types": "./build/index.d.ts" } diff --git a/packages/wdio-dot-reporter/src/index.ts b/packages/wdio-dot-reporter/src/index.ts index 3118f78c566..b5218abfc51 100644 --- a/packages/wdio-dot-reporter/src/index.ts +++ b/packages/wdio-dot-reporter/src/index.ts @@ -1,11 +1,12 @@ import chalk from 'chalk' -import WDIOReporter, { WDIOReporterOptions } from '@wdio/reporter' +import WDIOReporter from '@wdio/reporter' +import type { Reporters } from '@wdio/types' /** * Initialize a new `Dot` matrix test reporter. */ export default class DotReporter extends WDIOReporter { - constructor(options: Partial) { + constructor(options: Reporters.Options) { super(Object.assign({ stdout: true }, options)) } diff --git a/packages/wdio-dot-reporter/tsconfig.json b/packages/wdio-dot-reporter/tsconfig.json index a32652092d3..d660189bdb9 100644 --- a/packages/wdio-dot-reporter/tsconfig.json +++ b/packages/wdio-dot-reporter/tsconfig.json @@ -6,6 +6,7 @@ "rootDir": "./src" }, "include": [ - "src/**/*" + "src/**/*", + "../../@types" ] } diff --git a/packages/wdio-dot-reporter/tsconfig.prod.json b/packages/wdio-dot-reporter/tsconfig.prod.json index c7e2194a8b4..2a0e7e84140 100644 --- a/packages/wdio-dot-reporter/tsconfig.prod.json +++ b/packages/wdio-dot-reporter/tsconfig.prod.json @@ -6,6 +6,7 @@ "rootDir": "./src" }, "include": [ - "src/**/*" + "src/**/*", + "../../@types" ] } diff --git a/packages/wdio-firefox-profile-service/firefox-profile-service.d.ts b/packages/wdio-firefox-profile-service/firefox-profile-service.d.ts deleted file mode 100644 index 230f64512b3..00000000000 --- a/packages/wdio-firefox-profile-service/firefox-profile-service.d.ts +++ /dev/null @@ -1,5 +0,0 @@ -import type { Options } from './build/types' - -declare module WebdriverIO { - interface ServiceOption extends Options {} -} diff --git a/packages/wdio-firefox-profile-service/package.json b/packages/wdio-firefox-profile-service/package.json index 5f3caeb2e90..c61bbd5b84a 100644 --- a/packages/wdio-firefox-profile-service/package.json +++ b/packages/wdio-firefox-profile-service/package.json @@ -29,6 +29,7 @@ "url": "https://github.com/webdriverio/webdriverio/issues" }, "dependencies": { + "@wdio/types": "^6.10.6", "firefox-profile": "^4.0.0" }, "peerDependencies": { @@ -37,5 +38,5 @@ "publishConfig": { "access": "public" }, - "types": "firefox-profile-service.d.ts" + "types": "./build/index.d.ts" } diff --git a/packages/wdio-firefox-profile-service/src/index.ts b/packages/wdio-firefox-profile-service/src/index.ts index edfa1cd735d..1b9cbb8ac52 100644 --- a/packages/wdio-firefox-profile-service/src/index.ts +++ b/packages/wdio-firefox-profile-service/src/index.ts @@ -1,4 +1,13 @@ /* istanbul ignore file */ import FirefoxProfileLauncher from './launcher' +import type { FirefoxProfileOptions } from './types' + export const launcher = FirefoxProfileLauncher +export * from './types' + +declare global { + namespace WebdriverIO { + interface ServiceOption extends FirefoxProfileOptions {} + } +} diff --git a/packages/wdio-firefox-profile-service/src/launcher.ts b/packages/wdio-firefox-profile-service/src/launcher.ts index 3ac0b5b3b33..0db2628219e 100644 --- a/packages/wdio-firefox-profile-service/src/launcher.ts +++ b/packages/wdio-firefox-profile-service/src/launcher.ts @@ -1,13 +1,14 @@ import Profile from 'firefox-profile' import { promisify } from 'util' +import type { Capabilities } from '@wdio/types' -import { Options } from './types' +import { FirefoxProfileOptions } from './types' export default class FirefoxProfileLauncher { private _profile?: Profile - constructor(private _options: Options) {} + constructor(private _options: FirefoxProfileOptions) {} - async onPrepare(config: WebdriverIO.Config, capabilities: WebDriver.DesiredCapabilities[] | WebdriverIO.MultiRemoteCapabilities) { + async onPrepare(config: never, capabilities: Capabilities.RemoteCapabilities) { /** * Return if no profile options were specified */ @@ -60,7 +61,7 @@ export default class FirefoxProfileLauncher { this._profile.updatePreferences() } - async _buildExtension(capabilities: WebDriver.DesiredCapabilities[] | WebdriverIO.MultiRemoteCapabilities) { + async _buildExtension(capabilities: Capabilities.RemoteCapabilities) { if (!this._profile) { return } @@ -68,7 +69,7 @@ export default class FirefoxProfileLauncher { const zippedProfile = await promisify(this._profile.encoded.bind(this._profile))() if (Array.isArray(capabilities)) { - capabilities + (capabilities as Capabilities.DesiredCapabilities[]) .filter((capability) => capability.browserName === 'firefox') .forEach((capability) => { this._setProfile(capability, zippedProfile) @@ -78,7 +79,7 @@ export default class FirefoxProfileLauncher { } for (const browser in capabilities) { - const capability = capabilities[browser].capabilities + const capability = capabilities[browser].capabilities as Capabilities.DesiredCapabilities if (!capability || capability.browserName !== 'firefox') { continue @@ -88,7 +89,7 @@ export default class FirefoxProfileLauncher { } } - _setProfile(capability: WebDriver.DesiredCapabilities, zippedProfile: string) { + _setProfile(capability: Capabilities.Capabilities, zippedProfile: string) { if (this._options.legacy) { // for older firefox and geckodriver versions capability.firefox_profile = zippedProfile diff --git a/packages/wdio-firefox-profile-service/src/types.ts b/packages/wdio-firefox-profile-service/src/types.ts index 7652d0571ca..482b51dac48 100644 --- a/packages/wdio-firefox-profile-service/src/types.ts +++ b/packages/wdio-firefox-profile-service/src/types.ts @@ -25,7 +25,7 @@ interface ManualProxySettings { type ProxySettings = NoProxySettings | SystemProxySettings | AutomaticProxySettings | ManualProxySettings; -export interface Options extends FirefoxSettings { +export interface FirefoxProfileOptions extends FirefoxSettings { /** * Add one or multiple extensions to the browser session. All entries can be either an absolute path to the `.xpi` * file or the path to an unpacked Firefox extension directory. diff --git a/packages/wdio-firefox-profile-service/tsconfig.json b/packages/wdio-firefox-profile-service/tsconfig.json index 40b1b6eecec..d660189bdb9 100644 --- a/packages/wdio-firefox-profile-service/tsconfig.json +++ b/packages/wdio-firefox-profile-service/tsconfig.json @@ -3,10 +3,10 @@ "compilerOptions": { "baseUrl": ".", "outDir": "./build", - "rootDir": "./src", - "types": ["webdriver", "webdriverio"] + "rootDir": "./src" }, "include": [ - "src/**/*" + "src/**/*", + "../../@types" ] } diff --git a/packages/wdio-firefox-profile-service/tsconfig.prod.json b/packages/wdio-firefox-profile-service/tsconfig.prod.json index 3ba79666a28..2a0e7e84140 100644 --- a/packages/wdio-firefox-profile-service/tsconfig.prod.json +++ b/packages/wdio-firefox-profile-service/tsconfig.prod.json @@ -3,10 +3,10 @@ "compilerOptions": { "baseUrl": ".", "outDir": "./build", - "rootDir": "./src", - "types": ["webdriver", "webdriverio"] + "rootDir": "./src" }, "include": [ - "src/**/*" + "src/**/*", + "../../@types" ] } diff --git a/packages/wdio-jasmine-framework/jasmine-framework.d.ts b/packages/wdio-jasmine-framework/jasmine-framework.d.ts deleted file mode 100644 index 47a03093da8..00000000000 --- a/packages/wdio-jasmine-framework/jasmine-framework.d.ts +++ /dev/null @@ -1,90 +0,0 @@ -/// -/// - -declare module WebdriverIO { - interface Config extends JasmineNodeOptsConfig {} - - interface Suite { - title: string; - fullName: string; - file: string; - } - - interface Test extends Suite { - parent: string; - passed: boolean; - } -} - -interface JasmineNodeOptsConfig { - jasmineNodeOpts?: JasmineNodeOpts; -} - -interface JasmineNodeOpts { - /** - * Default Timeout Interval for Jasmine operations. - * @default `60000` - */ - defaultTimeoutInterval?: number; - /** - * Array of filepaths (and globs) relative to spec_dir to include before - * jasmine specs. - * @default `[]` - */ - helpers?: string[]; - /** - * The `requires` option is useful when you want to add or extend some - * basic functionality. - * @default `[]` - */ - requires?: string[]; - /** - * Whether to stop execution of the suite after the first spec failure - * Since Jasmine v3.3.0 - * @default false - */ - failFast?: boolean; - /** - * Whether to fail the spec if it ran no expectations. By default a spec - * that ran no expectations is reported as passed. Setting this to true - * will report such spec as a failure. - * Since Jasmine v3.5.0 - * @default false - */ - failSpecWithNoExpectations?: boolean; - /** - * Whether to cause specs to only have one expectation failure. - * Since Jasmine v3.3.0 - * @default false - */ - oneFailurePerSpec?: boolean; - /** - * Whether to randomize spec execution order. - * Since Jasmine v3.3.0 - * @default false - */ - random?: boolean; - /** - * Seed to use as the basis of randomization. Null causes the seed to be - * determined randomly at the start of execution. - * Since Jasmine v3.3.0 - * @default null - */ - seed?: Function; - /** - * Function to use to filter specs. - * Since Jasmine v3.3.0 - * @default true - */ - specFilter?: Function; - /** - * Optional pattern to selectively select it/describe cases to run from spec files. - * @default null - */ - grep?: string | RegExp - /** - * Inverts 'grep' matches. - * @default null - */ - invertGrep?: string | RegExp -} diff --git a/packages/wdio-jasmine-framework/package.json b/packages/wdio-jasmine-framework/package.json index c1da7e55e4f..ccfe72d72eb 100644 --- a/packages/wdio-jasmine-framework/package.json +++ b/packages/wdio-jasmine-framework/package.json @@ -29,6 +29,7 @@ }, "dependencies": { "@wdio/logger": "6.10.10", + "@wdio/types": "^6.10.6", "@wdio/utils": "6.11.0", "expect-webdriverio": "^1.1.5", "jasmine": "3.6.3" @@ -39,5 +40,5 @@ "publishConfig": { "access": "public" }, - "types": "jasmine-framework.d.ts" + "types": "./build/index.d.ts" } diff --git a/packages/wdio-jasmine-framework/src/index.ts b/packages/wdio-jasmine-framework/src/index.ts index 10e613f3124..b2eb1d0d326 100644 --- a/packages/wdio-jasmine-framework/src/index.ts +++ b/packages/wdio-jasmine-framework/src/index.ts @@ -1,7 +1,10 @@ +/// + import Jasmine from 'jasmine' import { runTestInFiberContext, executeHooksWithArgs } from '@wdio/utils' import logger from '@wdio/logger' import { EventEmitter } from 'events' +import type { Options, Services, Capabilities } from '@wdio/types' import JasmineReporter from './reporter' import type { JasmineNodeOpts, ResultHandlerPayload, FrameworkMessage, FormattedMessage } from './types' @@ -17,10 +20,10 @@ const DEFAULT_TIMEOUT_INTERVAL = 60000 const log = logger('@wdio/jasmine-framework') type HooksArray = { - [K in keyof Required]: Required[K][] + [K in keyof Required]: Required[K][] } -interface WebdriverIOJasmineConfig extends Omit, HooksArray { +interface WebdriverIOJasmineConfig extends Omit, HooksArray { jasmineNodeOpts: Omit } @@ -42,7 +45,7 @@ class JasmineAdapter { private _cid: string, private _config: WebdriverIOJasmineConfig, private _specs: string[], - private _capabilities: WebDriver.Capabilities, + private _capabilities: Capabilities.RemoteCapabilities, reporter: EventEmitter ) { this._jasmineNodeOpts = Object.assign({ @@ -264,7 +267,7 @@ class JasmineAdapter { /** * Hooks which are added as true Jasmine hooks need to call done() to notify async */ - wrapHook (hookName: keyof WebdriverIO.HookFunctions) { + wrapHook (hookName: keyof Services.HookFunctions) { return (done: Function) => executeHooksWithArgs( hookName, this._config[hookName], @@ -275,7 +278,7 @@ class JasmineAdapter { }) } - prepareMessage (hookName: keyof WebdriverIO.HookFunctions) { + prepareMessage (hookName: keyof Services.HookFunctions) { const params: FrameworkMessage = { type: hookName } switch (hookName) { @@ -374,3 +377,10 @@ adapterFactory.init = async function (...args: any[]) { export default adapterFactory export { JasmineAdapter, adapterFactory } +export * from './types' + +declare global { + namespace WebdriverIO { + interface JasmineOpts extends JasmineNodeOpts {} + } +} diff --git a/packages/wdio-jasmine-framework/tests/adapter.test.ts b/packages/wdio-jasmine-framework/tests/adapter.test.ts index e5b5a94c45b..a1ea558dd70 100644 --- a/packages/wdio-jasmine-framework/tests/adapter.test.ts +++ b/packages/wdio-jasmine-framework/tests/adapter.test.ts @@ -31,7 +31,7 @@ const adapterFactory = (config = {}) => new JasmineAdapter( '0-2', { beforeHook: [], afterHook: [], beforeTest: 'beforeTest', afterTest: 'afterTest', ...config } as any, ['/foo/bar.test.js'], - { browserName: 'chrome' }, + { browserName: 'chrome' } as any, wdioReporter ) diff --git a/packages/wdio-jasmine-framework/tsconfig.json b/packages/wdio-jasmine-framework/tsconfig.json index e0952f8f6bb..d660189bdb9 100644 --- a/packages/wdio-jasmine-framework/tsconfig.json +++ b/packages/wdio-jasmine-framework/tsconfig.json @@ -3,10 +3,10 @@ "compilerOptions": { "baseUrl": ".", "outDir": "./build", - "rootDir": "./src", - "types": ["webdriverio"] + "rootDir": "./src" }, "include": [ - "src/**/*" + "src/**/*", + "../../@types" ] } diff --git a/packages/wdio-jasmine-framework/tsconfig.prod.json b/packages/wdio-jasmine-framework/tsconfig.prod.json index 8ff66adac36..2a0e7e84140 100644 --- a/packages/wdio-jasmine-framework/tsconfig.prod.json +++ b/packages/wdio-jasmine-framework/tsconfig.prod.json @@ -3,10 +3,10 @@ "compilerOptions": { "baseUrl": ".", "outDir": "./build", - "rootDir": "./src", - "types": ["webdriverio"] + "rootDir": "./src" }, "include": [ - "src/**/*" + "src/**/*", + "../../@types" ] } diff --git a/packages/wdio-junit-reporter/tsconfig.json b/packages/wdio-junit-reporter/tsconfig.json index a32652092d3..d660189bdb9 100644 --- a/packages/wdio-junit-reporter/tsconfig.json +++ b/packages/wdio-junit-reporter/tsconfig.json @@ -6,6 +6,7 @@ "rootDir": "./src" }, "include": [ - "src/**/*" + "src/**/*", + "../../@types" ] } diff --git a/packages/wdio-junit-reporter/tsconfig.prod.json b/packages/wdio-junit-reporter/tsconfig.prod.json index c7e2194a8b4..2a0e7e84140 100644 --- a/packages/wdio-junit-reporter/tsconfig.prod.json +++ b/packages/wdio-junit-reporter/tsconfig.prod.json @@ -6,6 +6,7 @@ "rootDir": "./src" }, "include": [ - "src/**/*" + "src/**/*", + "../../@types" ] } diff --git a/packages/wdio-local-runner/package.json b/packages/wdio-local-runner/package.json index 07a70b1d77a..b5ab8a0308e 100644 --- a/packages/wdio-local-runner/package.json +++ b/packages/wdio-local-runner/package.json @@ -27,6 +27,7 @@ "@wdio/logger": "6.10.10", "@wdio/repl": "6.11.0", "@wdio/runner": "6.12.1", + "@wdio/types": "^6.10.6", "async-exit-hook": "^2.0.1", "stream-buffers": "^3.0.2" }, @@ -35,5 +36,6 @@ }, "publishConfig": { "access": "public" - } + }, + "types": "./build/index.d.ts" } diff --git a/packages/wdio-local-runner/src/index.ts b/packages/wdio-local-runner/src/index.ts index b9ef3bb4bc7..b7e7c48492b 100644 --- a/packages/wdio-local-runner/src/index.ts +++ b/packages/wdio-local-runner/src/index.ts @@ -1,5 +1,6 @@ import logger from '@wdio/logger' import { WritableStreamBuffer } from 'stream-buffers' +import type { Options } from '@wdio/types' import WorkerInstance from './worker' import { SHUTDOWN_TIMEOUT, BUFFER_OPTIONS } from './constants' @@ -13,15 +14,15 @@ interface RunArgs extends WorkerRunPayload { } export default class LocalRunner { - config: WebdriverIO.Config workerPool: Record = {} stdout = new WritableStreamBuffer(BUFFER_OPTIONS) stderr = new WritableStreamBuffer(BUFFER_OPTIONS) - constructor (configFile: string, config: WebdriverIO.Config) { - this.config = config - } + constructor ( + configFile: unknown, + private _config: Options.Testrunner + ) {} /** * nothing to initialise when running locally @@ -42,7 +43,7 @@ export default class LocalRunner { process.stderr.setMaxListeners(workerCnt + 2) } - const worker = new WorkerInstance(this.config, workerOptions, this.stdout, this.stderr) + const worker = new WorkerInstance(this._config, workerOptions, this.stdout, this.stderr) this.workerPool[workerOptions.cid] = worker worker.postMessage(command, args) diff --git a/packages/wdio-local-runner/src/types.ts b/packages/wdio-local-runner/src/types.ts index 748b0b3f3c2..ad4ab4fbd1e 100644 --- a/packages/wdio-local-runner/src/types.ts +++ b/packages/wdio-local-runner/src/types.ts @@ -1,7 +1,9 @@ +import type { Capabilities } from '@wdio/types' + export interface WorkerRunPayload { cid: string, configFile: string, - caps: WebDriver.Capabilities, + caps: Capabilities.RemoteCapability, specs: string[], execArgv: string[], retries: number diff --git a/packages/wdio-local-runner/src/worker.ts b/packages/wdio-local-runner/src/worker.ts index 595b606bb35..97091ad8974 100644 --- a/packages/wdio-local-runner/src/worker.ts +++ b/packages/wdio-local-runner/src/worker.ts @@ -3,6 +3,7 @@ import child from 'child_process' import { EventEmitter } from 'events' import type { WritableStreamBuffer } from 'stream-buffers' import type { ChildProcess } from 'child_process' +import type { Capabilities, Options } from '@wdio/types' import logger from '@wdio/logger' @@ -27,9 +28,9 @@ stdErrStream.pipe(process.stderr) */ export default class WorkerInstance extends EventEmitter { cid: string - config: WebdriverIO.Config + config: Options.Testrunner configFile: string - caps: WebDriver.Capabilities + caps: Capabilities.RemoteCapability specs: string[] execArgv: string[] retries: number @@ -55,7 +56,7 @@ export default class WorkerInstance extends EventEmitter { * @param {object} execArgv execution arguments for the test run */ constructor( - config: WebdriverIO.Config, + config: Options.Testrunner, { cid, configFile, caps, specs, execArgv, retries }: WorkerRunPayload, stdout: WritableStreamBuffer, stderr: WritableStreamBuffer diff --git a/packages/wdio-local-runner/tests/localRunner.test.ts b/packages/wdio-local-runner/tests/localRunner.test.ts index 12fa0d58fbf..e532442b50a 100644 --- a/packages/wdio-local-runner/tests/localRunner.test.ts +++ b/packages/wdio-local-runner/tests/localRunner.test.ts @@ -16,7 +16,7 @@ test('should fork a new process', () => { const runner = new LocalRunner('/path/to/wdio.conf.js', { outputDir: '/foo/bar', runnerEnv: { FORCE_COLOR: 1 } - }) + } as any) const worker = runner.run({ cid: '0-5', command: 'run', @@ -36,9 +36,9 @@ test('should fork a new process', () => { const { env } = (child.fork as jest.Mock).mock.calls[0][2] expect(env.WDIO_LOG_PATH).toMatch(/(\\|\/)foo(\\|\/)bar(\\|\/)wdio-0-5\.log/) expect(env.FORCE_COLOR).toBe(1) - expect(childProcess?.on).toBeCalled() + expect(childProcess?.on).toHaveBeenCalled() - expect(childProcess?.send).toBeCalledWith({ + expect(childProcess?.send).toHaveBeenCalledWith({ args: {}, caps: {}, cid: '0-5', @@ -55,7 +55,7 @@ test('should shut down worker processes', async () => { const runner = new LocalRunner('/path/to/wdio.conf.js', { outputDir: '/foo/bar', runnerEnv: { FORCE_COLOR: 1 } - }) + } as any) const worker1 = runner.run({ cid: '0-4', command: 'run', @@ -100,7 +100,7 @@ test('should avoid shutting down if worker is not busy', async () => { const runner = new LocalRunner('/path/to/wdio.conf.js', { outputDir: '/foo/bar', runnerEnv: { FORCE_COLOR: 1 } - }) + } as any) runner.run({ cid: '0-8', @@ -124,7 +124,7 @@ test('should shut down worker processes in watch mode - regular', async () => { outputDir: '/foo/bar', runnerEnv: { FORCE_COLOR: 1 }, watch: true, - }) + } as any) const worker = runner.run({ cid: '0-6', @@ -138,7 +138,7 @@ test('should shut down worker processes in watch mode - regular', async () => { }) runner.workerPool['0-6'].sessionId = 'abc' runner.workerPool['0-6'].server = { host: 'foo' } - runner.workerPool['0-6'].caps = { browser: 'chrome' } as WebDriver.Capabilities + runner.workerPool['0-6'].caps = { browser: 'chrome' } as any setTimeout(() => { worker.isBusy = false @@ -165,7 +165,7 @@ test('should shut down worker processes in watch mode - mutliremote', async () = outputDir: '/foo/bar', runnerEnv: { FORCE_COLOR: 1 }, watch: true, - }) + } as any) const worker = runner.run({ cid: '0-7', @@ -183,7 +183,7 @@ test('should shut down worker processes in watch mode - mutliremote', async () = foo: { capabilities: { browser: 'chrome' } } - } as WebdriverIO.MultiRemoteCapabilities + } as any setTimeout(() => { worker.isBusy = false @@ -205,6 +205,6 @@ test('should shut down worker processes in watch mode - mutliremote', async () = }) test('should avoid shutting down if worker is not busy', async () => { - const runner = new LocalRunner('/path/to/wdio.conf.js', {}) + const runner = new LocalRunner('/path/to/wdio.conf.js', {} as any) expect(runner.initialise()).toBe(undefined) }) diff --git a/packages/wdio-local-runner/tests/repl.test.ts b/packages/wdio-local-runner/tests/repl.test.ts index 5b9442e1c61..994a8601b4c 100644 --- a/packages/wdio-local-runner/tests/repl.test.ts +++ b/packages/wdio-local-runner/tests/repl.test.ts @@ -27,7 +27,7 @@ test('should send child process message that debugger has started', () => { const repl = new WDIORunnerRepl(childProcess as unknown as ChildProcess, replConfig) repl.start({}) expect(childProcess.send) - .toBeCalledWith({ origin: 'debugger', name: 'start' }) + .toHaveBeenCalledWith({ origin: 'debugger', name: 'start' }) }) test('should send command to child process', () => { @@ -39,7 +39,7 @@ test('should send command to child process', () => { expect(repl.callback).toBe(undefined) repl.eval('1+1', {}, '/foo/bar', callback) expect(repl.commandIsRunning).toBe(true) - expect(childProcess.send).toBeCalledWith({ + expect(childProcess.send).toHaveBeenCalledWith({ origin: 'debugger', name: 'eval', content: { cmd: '1+1' } @@ -47,7 +47,7 @@ test('should send command to child process', () => { expect(typeof repl.callback).toBe('function') repl.callback!(null, {}) - expect(callback).toBeCalled() + expect(callback).toHaveBeenCalled() }) test('should not send command if command is already running', () => { @@ -57,7 +57,7 @@ test('should not send command if command is already running', () => { repl.commandIsRunning = true repl.eval('1+1', {}, '/foo/bar', callback) - expect(childProcess.send).toBeCalledTimes(0) + expect(childProcess.send).toHaveBeenCalledTimes(0) }) test('should pass in result to callback', () => { @@ -67,7 +67,7 @@ test('should pass in result to callback', () => { repl.commandIsRunning = true repl.onResult({ result: 'foobar' }) - expect(repl.callback).toBeCalledWith(null, 'foobar') + expect(repl.callback).toHaveBeenCalledWith(null, 'foobar') }) test('should switch flag even if no callback is set', () => { diff --git a/packages/wdio-local-runner/tests/replQueue.test.ts b/packages/wdio-local-runner/tests/replQueue.test.ts index 6482fd975c3..f8294d613ff 100644 --- a/packages/wdio-local-runner/tests/replQueue.test.ts +++ b/packages/wdio-local-runner/tests/replQueue.test.ts @@ -9,7 +9,7 @@ test('add', () => { expect(queue['_repls']).toEqual([ { childProcess: 1, options: 2, onStart: 3, onEnd: 4 }, { childProcess: 5, options: 6, onStart: 7, onEnd: 8 } - ]) + ] as any) }) test('isRunning', () => { @@ -31,21 +31,21 @@ test('next', async () => { queue.add(childProcess2, { some: 'option' }, startFn2, endFn2) queue.next() - expect(startFn).toBeCalledTimes(1) + expect(startFn).toHaveBeenCalledTimes(1) expect(queue.isRunning).toBe(true) queue.next() - expect(startFn2).toBeCalledTimes(0) + expect(startFn2).toHaveBeenCalledTimes(0) // wait 100ms to let repl finish (see mock) await new Promise((resolve) => setTimeout(resolve, 100)) - expect(childProcess.send).toBeCalledWith({ + expect(childProcess.send).toHaveBeenCalledWith({ origin: 'debugger', name: 'stop' }) - expect(endFn).toBeCalledTimes(1) - expect(startFn2).toBeCalledTimes(1) - expect(endFn2).toBeCalledTimes(0) + expect(endFn).toHaveBeenCalledTimes(1) + expect(startFn2).toHaveBeenCalledTimes(1) + expect(endFn2).toHaveBeenCalledTimes(0) await new Promise((resolve) => setTimeout(resolve, 100)) expect(queue.isRunning).toBe(false) diff --git a/packages/wdio-local-runner/tests/run.test.ts b/packages/wdio-local-runner/tests/run.test.ts index 3efede3f05c..89f84c2f21f 100644 --- a/packages/wdio-local-runner/tests/run.test.ts +++ b/packages/wdio-local-runner/tests/run.test.ts @@ -2,6 +2,8 @@ import exitHook from 'async-exit-hook' // @ts-ignore mock exports instances, package doesn't import { instances } from '@wdio/runner' +const expect = global.expect as unknown as jest.Expect + jest.mock('../src/constants', () => ({ SHUTDOWN_TIMEOUT: 1 })) diff --git a/packages/wdio-local-runner/tests/stdStream.test.ts b/packages/wdio-local-runner/tests/stdStream.test.ts index 68f1a06ca75..c3a1f7164b3 100644 --- a/packages/wdio-local-runner/tests/stdStream.test.ts +++ b/packages/wdio-local-runner/tests/stdStream.test.ts @@ -1,5 +1,7 @@ import RunnerStream from '../src/stdStream' +const expect = global.expect as unknown as jest.Expect + describe('RunnerStream', () => { let stream: RunnerStream let pushSpy: jest.SpyInstance diff --git a/packages/wdio-local-runner/tests/transformStream.test.ts b/packages/wdio-local-runner/tests/transformStream.test.ts index 401c6b403e5..5a62f09fdfa 100644 --- a/packages/wdio-local-runner/tests/transformStream.test.ts +++ b/packages/wdio-local-runner/tests/transformStream.test.ts @@ -1,6 +1,8 @@ import RunnerTransformStream from '../src/transformStream' import { DEBUGGER_MESSAGES } from '../src/constants' +const expect = global.expect as unknown as jest.Expect + test('should add cid to message', () => { const stream = new RunnerTransformStream('0-5') const cb = jest.fn() diff --git a/packages/wdio-local-runner/tests/utils.test.ts b/packages/wdio-local-runner/tests/utils.test.ts index 921fddc2c30..549bdf2f999 100644 --- a/packages/wdio-local-runner/tests/utils.test.ts +++ b/packages/wdio-local-runner/tests/utils.test.ts @@ -1,6 +1,8 @@ import RunnerStream from '../src/stdStream' import { removeLastListener } from '../src/utils' +const expect = global.expect as unknown as jest.Expect + describe('removeLastListener', () => { it('should remove only last listener', () => { const stream = new RunnerStream() diff --git a/packages/wdio-local-runner/tests/worker.test.ts b/packages/wdio-local-runner/tests/worker.test.ts index 2340ea99511..3a0eda0694d 100644 --- a/packages/wdio-local-runner/tests/worker.test.ts +++ b/packages/wdio-local-runner/tests/worker.test.ts @@ -6,6 +6,8 @@ import logger from '@wdio/logger' import Worker from '../src/worker' import type { WorkerMessage } from '../src/types' +const expect = global.expect as unknown as jest.Expect + const workerConfig = { cid: '0-3', configFile: '/foobar', @@ -17,7 +19,7 @@ const workerConfig = { describe('handleMessage', () => { it('should emit payload with cid', () => { - const worker = new Worker({}, workerConfig, new WritableStreamBuffer(), new WritableStreamBuffer()) + const worker = new Worker({} as any, workerConfig, new WritableStreamBuffer(), new WritableStreamBuffer()) worker.emit = jest.fn() worker['_handleMessage']({ foo: 'bar' } as unknown as WorkerMessage) @@ -28,14 +30,14 @@ describe('handleMessage', () => { }) it('should un mark worker as busy if command is finished', () => { - const worker = new Worker({}, workerConfig, new WritableStreamBuffer(), new WritableStreamBuffer()) + const worker = new Worker({} as any, workerConfig, new WritableStreamBuffer(), new WritableStreamBuffer()) worker.isBusy = true worker['_handleMessage']({ name: 'finisedCommand' } as unknown as WorkerMessage) expect(worker.isBusy).toBe(false) }) it('stores sessionId and connection data to worker instance', () => { - const worker = new Worker({}, workerConfig, new WritableStreamBuffer(), new WritableStreamBuffer()) + const worker = new Worker({} as any, workerConfig, new WritableStreamBuffer(), new WritableStreamBuffer()) worker.emit = jest.fn() const payload = { name: 'sessionStarted', @@ -51,7 +53,7 @@ describe('handleMessage', () => { }) it('stores instances to worker instance in Multiremote mode', () => { - const worker = new Worker({}, workerConfig, new WritableStreamBuffer(), new WritableStreamBuffer()) + const worker = new Worker({} as any, workerConfig, new WritableStreamBuffer(), new WritableStreamBuffer()) const payload = { name: 'sessionStarted', content: { @@ -65,7 +67,7 @@ describe('handleMessage', () => { }) it('handle debug command called within worker process', async () => { - const worker = new Worker({}, workerConfig, new WritableStreamBuffer(), new WritableStreamBuffer()) + const worker = new Worker({} as any, workerConfig, new WritableStreamBuffer(), new WritableStreamBuffer()) worker.emit = jest.fn() worker.childProcess = { send: jest.fn() } as unknown as ChildProcess worker['_handleMessage']({ @@ -87,7 +89,7 @@ describe('handleMessage', () => { describe('handleError', () => { it('should emit error', () => { - const worker = new Worker({}, workerConfig, new WritableStreamBuffer(), new WritableStreamBuffer()) + const worker = new Worker({} as any, workerConfig, new WritableStreamBuffer(), new WritableStreamBuffer()) worker.emit = jest.fn() worker['_handleError']({ foo: 'bar' } as unknown as Error) expect(worker.emit).toBeCalledWith('error', { @@ -99,7 +101,7 @@ describe('handleError', () => { describe('handleExit', () => { it('should handle it', () => { - const worker = new Worker({}, workerConfig, new WritableStreamBuffer(), new WritableStreamBuffer()) + const worker = new Worker({} as any, workerConfig, new WritableStreamBuffer(), new WritableStreamBuffer()) const childProcess = { kill: jest.fn() } worker.childProcess = childProcess as unknown as ChildProcess worker.isBusy = true @@ -119,7 +121,7 @@ describe('handleExit', () => { describe('postMessage', () => { it('should log if the cid is busy and exit', () => { - const worker = new Worker({}, workerConfig, new WritableStreamBuffer(), new WritableStreamBuffer()) + const worker = new Worker({} as any, workerConfig, new WritableStreamBuffer(), new WritableStreamBuffer()) const log = logger('webdriver') jest.spyOn(log, 'info').mockImplementation((string) => string) @@ -131,7 +133,7 @@ describe('postMessage', () => { }) it('should create a process if it does not have one', () => { - const worker = new Worker({}, workerConfig, new WritableStreamBuffer(), new WritableStreamBuffer()) + const worker = new Worker({} as any, workerConfig, new WritableStreamBuffer(), new WritableStreamBuffer()) worker.childProcess = undefined jest.spyOn(worker, 'startProcess').mockImplementation( () => ({ send: jest.fn() }) as unknown as ChildProcess) diff --git a/packages/wdio-local-runner/tsconfig.json b/packages/wdio-local-runner/tsconfig.json index 5620bc2b89c..d660189bdb9 100644 --- a/packages/wdio-local-runner/tsconfig.json +++ b/packages/wdio-local-runner/tsconfig.json @@ -3,10 +3,10 @@ "compilerOptions": { "baseUrl": ".", "outDir": "./build", - "rootDir": "./src", - "types": ["webdriverio", "node"] + "rootDir": "./src" }, "include": [ - "src/**/*" + "src/**/*", + "../../@types" ] } diff --git a/packages/wdio-local-runner/tsconfig.prod.json b/packages/wdio-local-runner/tsconfig.prod.json index 8ff66adac36..2a0e7e84140 100644 --- a/packages/wdio-local-runner/tsconfig.prod.json +++ b/packages/wdio-local-runner/tsconfig.prod.json @@ -3,10 +3,10 @@ "compilerOptions": { "baseUrl": ".", "outDir": "./build", - "rootDir": "./src", - "types": ["webdriverio"] + "rootDir": "./src" }, "include": [ - "src/**/*" + "src/**/*", + "../../@types" ] } diff --git a/packages/wdio-logger/tsconfig.json b/packages/wdio-logger/tsconfig.json index a32652092d3..d660189bdb9 100644 --- a/packages/wdio-logger/tsconfig.json +++ b/packages/wdio-logger/tsconfig.json @@ -6,6 +6,7 @@ "rootDir": "./src" }, "include": [ - "src/**/*" + "src/**/*", + "../../@types" ] } diff --git a/packages/wdio-logger/tsconfig.prod.json b/packages/wdio-logger/tsconfig.prod.json index c7e2194a8b4..2a0e7e84140 100644 --- a/packages/wdio-logger/tsconfig.prod.json +++ b/packages/wdio-logger/tsconfig.prod.json @@ -6,6 +6,7 @@ "rootDir": "./src" }, "include": [ - "src/**/*" + "src/**/*", + "../../@types" ] } diff --git a/packages/wdio-mocha-framework/mocha-framework.d.ts b/packages/wdio-mocha-framework/mocha-framework.d.ts deleted file mode 100644 index c9f09e93d86..00000000000 --- a/packages/wdio-mocha-framework/mocha-framework.d.ts +++ /dev/null @@ -1,104 +0,0 @@ -/// -/// - -declare module WebdriverIO { - interface Config extends MochaOptsConfig {} - - interface Suite { - file: string; - title: string; - parent: string; - fullTitle: string; - pending: boolean; - } - - interface Test extends Suite { - currentTest: string; - passed: boolean; - duration?: number; - error?: { - actual?: any; - expected?: any; - message: string; - stack?: string; - type: string; - } - } -} - -interface MochaOptsConfig { - mochaOpts?: MochaOpts -} - -interface MochaOpts { - /** - * The `require` option is useful when you want to add or extend some - * basic functionality (WebdriverIO framework option). - */ - require?: string | string[], - /** - * Use the given module(s) to compile files. Compilers will be included - * before requires (WebdriverIO framework option). - */ - compilers?: string[], - /** - * Propagate uncaught errors? - */ - allowUncaught?: boolean; - /** - * Force done callback or promise? - */ - asyncOnly?: boolean; - /** - * Bail after first test failure? - */ - bail?: boolean; - /** - * Check for global variable leaks? - */ - checkLeaks?: boolean; - /** - * Delay root suite execution? - */ - delay?: boolean; - /** - * Test filter given string. - */ - fgrep?: string; - /** - * Tests marked only fail the suite? - */ - forbidOnly?: boolean; - /** - * Pending tests fail the suite? - */ - forbidPending?: boolean; - /** - * Full stacktrace upon failure? - */ - fullTrace?: boolean; - /** - * Variables expected in global scope. - */ - global?: string[]; - /** - * Test filter given regular expression. - */ - grep?: RegExp | string; - /** - * Invert test filter matches? - */ - invert?: boolean; - /** - * Number of times to retry failed tests. - */ - retries?: number; - /** - * Timeout threshold value. - */ - timeout?: number | string; - /** - * Set test UI to one of the built-in test interfaces. - */ - ui?: 'bdd' | 'tdd' | 'qunit' | 'exports'; -} diff --git a/packages/wdio-mocha-framework/package.json b/packages/wdio-mocha-framework/package.json index 744f1d7db8e..02edb483b55 100644 --- a/packages/wdio-mocha-framework/package.json +++ b/packages/wdio-mocha-framework/package.json @@ -24,15 +24,13 @@ "dependencies": { "@types/mocha": "^8.0.0", "@wdio/logger": "6.10.10", + "@wdio/types": "^6.10.6", "@wdio/utils": "6.11.0", "expect-webdriverio": "^1.1.5", "mocha": "^8.0.1" }, - "peerDependencies": { - "webdriverio": "^6.0.1" - }, "publishConfig": { "access": "public" }, - "types": "mocha-framework.d.ts" + "types": "./build/index.d.ts" } diff --git a/packages/wdio-mocha-framework/src/index.ts b/packages/wdio-mocha-framework/src/index.ts index 2f3365eef46..364f49243e1 100644 --- a/packages/wdio-mocha-framework/src/index.ts +++ b/packages/wdio-mocha-framework/src/index.ts @@ -4,10 +4,11 @@ import { format } from 'util' import logger from '@wdio/logger' import { runTestInFiberContext, executeHooksWithArgs } from '@wdio/utils' +import type { Capabilities, Services } from '@wdio/types' import { loadModule } from './utils' import { INTERFACES, EVENTS, NOOP, MOCHA_TIMEOUT_MESSAGE, MOCHA_TIMEOUT_MESSAGE_REPLACEMENT } from './constants' -import type { MochaConfig, MochaOpts, FrameworkMessage, FormattedMessage, MochaContext, MochaError } from './types' +import type { MochaConfig, MochaOpts as MochaOptsImport, FrameworkMessage, FormattedMessage, MochaContext, MochaError } from './types' import type { EventEmitter } from 'events' const log = logger('@wdio/mocha-framework') @@ -46,7 +47,7 @@ class MochaAdapter { private _cid: string, private _config: MochaConfig, private _specs: string[], - private _capabilities: WebDriver.Capabilities, + private _capabilities: Capabilities.RemoteCapability, private _reporter: EventEmitter ) { this._config = Object.assign({ @@ -78,7 +79,7 @@ class MochaAdapter { return this } - async _loadFiles (mochaOpts: MochaOpts) { + async _loadFiles (mochaOpts: MochaOptsImport) { try { await this._mocha!.loadFilesAsync() @@ -138,7 +139,7 @@ class MochaAdapter { } options ( - options: MochaOpts, + options: MochaOptsImport, context: MochaContext ) { let { require = [], compilers = [] } = options @@ -185,7 +186,7 @@ class MochaAdapter { /** * Hooks which are added as true Mocha hooks need to call done() to notify async */ - wrapHook (hookName: keyof WebdriverIO.HookFunctions) { + wrapHook (hookName: keyof Services.HookFunctions) { return () => executeHooksWithArgs( hookName, this._config[hookName] as Function, @@ -195,7 +196,7 @@ class MochaAdapter { }) } - prepareMessage (hookName: keyof WebdriverIO.HookFunctions) { + prepareMessage (hookName: keyof Services.HookFunctions) { const params: FrameworkMessage = { type: hookName } switch (hookName) { @@ -380,3 +381,9 @@ adapterFactory.init = async function (...args: any[]) { export default adapterFactory export { MochaAdapter, adapterFactory } + +declare global { + namespace WebdriverIO { + interface MochaOpts extends MochaOptsImport {} + } +} diff --git a/packages/wdio-mocha-framework/src/types.ts b/packages/wdio-mocha-framework/src/types.ts index 3cc2c924064..3ba3b99c060 100644 --- a/packages/wdio-mocha-framework/src/types.ts +++ b/packages/wdio-mocha-framework/src/types.ts @@ -1,10 +1,79 @@ -export interface MochaOpts extends Mocha.MochaOptions { - require?: string[] - compilers?: string[] - invert?: boolean +import type { Options } from '@wdio/types' + +export interface MochaOpts { + /** + * The `require` option is useful when you want to add or extend some + * basic functionality (WebdriverIO framework option). + */ + require?: string | string[], + /** + * Use the given module(s) to compile files. Compilers will be included + * before requires (WebdriverIO framework option). + */ + compilers?: string[], + /** + * Propagate uncaught errors? + */ + allowUncaught?: boolean; + /** + * Force done callback or promise? + */ + asyncOnly?: boolean; + /** + * Bail after first test failure? + */ + bail?: boolean; + /** + * Check for global variable leaks? + */ + checkLeaks?: boolean; + /** + * Delay root suite execution? + */ + delay?: boolean; + /** + * Test filter given string. + */ + fgrep?: string; + /** + * Tests marked only fail the suite? + */ + forbidOnly?: boolean; + /** + * Pending tests fail the suite? + */ + forbidPending?: boolean; + /** + * Full stacktrace upon failure? + */ + fullTrace?: boolean; + /** + * Variables expected in global scope. + */ + global?: string[]; + /** + * Test filter given regular expression. + */ + grep?: RegExp | string; + /** + * Invert test filter matches? + */ + invert?: boolean; + /** + * Number of times to retry failed tests. + */ + retries?: number; + /** + * Timeout threshold value. + */ + timeout?: number | string; + /** + * Set test UI to one of the built-in test interfaces. + */ + ui?: 'bdd' | 'tdd' | 'qunit' | 'exports'; } -export interface MochaConfig extends Required { +export interface MochaConfig extends Required { mochaOpts: MochaOpts } diff --git a/packages/wdio-mocha-framework/tests/utils.test.ts b/packages/wdio-mocha-framework/tests/utils.test.ts index 19134fd7e80..44250339991 100644 --- a/packages/wdio-mocha-framework/tests/utils.test.ts +++ b/packages/wdio-mocha-framework/tests/utils.test.ts @@ -1,11 +1,7 @@ import { loadModule } from '../src/utils' declare global { - module NodeJS { - interface Global { - foo: string - } - } + var foo: string | undefined } test('loadModule with existing package', () => { diff --git a/packages/wdio-mocha-framework/tsconfig.json b/packages/wdio-mocha-framework/tsconfig.json index a32652092d3..d660189bdb9 100644 --- a/packages/wdio-mocha-framework/tsconfig.json +++ b/packages/wdio-mocha-framework/tsconfig.json @@ -6,6 +6,7 @@ "rootDir": "./src" }, "include": [ - "src/**/*" + "src/**/*", + "../../@types" ] } diff --git a/packages/wdio-mocha-framework/tsconfig.prod.json b/packages/wdio-mocha-framework/tsconfig.prod.json index c7e2194a8b4..2a0e7e84140 100644 --- a/packages/wdio-mocha-framework/tsconfig.prod.json +++ b/packages/wdio-mocha-framework/tsconfig.prod.json @@ -6,6 +6,7 @@ "rootDir": "./src" }, "include": [ - "src/**/*" + "src/**/*", + "../../@types" ] } diff --git a/packages/wdio-protocols/index.d.ts b/packages/wdio-protocols/index.d.ts deleted file mode 100644 index dac9a9d7d9e..00000000000 --- a/packages/wdio-protocols/index.d.ts +++ /dev/null @@ -1,92 +0,0 @@ -declare namespace WDIOProtocols { - type CommandPath = 'string' - type CommandMethod = 'POST' | 'GET' | 'DELETE' - type Protocol = Record> - - /** - * describes a command endpoint - */ - interface CommandEndpoint { - /** - * command name - */ - command: string - /** - * command description - */ - description: string - /** - * link to specification reference - */ - ref: string - /** - * supported command parameters - */ - parameters: CommandParameters[] - /** - * variables within the command path (e.g. /:sessionId/element) - */ - variables?: CommandPathVariables[] - /** - * supported environments - */ - support?: SupportedEnvironments - /** - * set to true if command is only supported in Selenium Hub Node - */ - isHubCommand?: boolean, - /** - * information on return data - */ - returns?: CommandReturnObject - } - - interface CommandReturnObject { - type: string - name: string - description: string - } - - interface CommandPathVariables { - name: string - description: string - - /** - * the following are given for path variables, we should still define - * it as values are populated automatically - */ - required?: boolean - type?: string - } - - interface CommandParameters { - name: string, - type: string, - description: string, - required: boolean - } - - type Platform = 'ios' | 'android' - type Environments = 'XCUITest' | 'UIAutomation' | 'UiAutomator' - - /** - * supported mobile environments, e.g. - * ``` - * "ios": { - * "UIAutomation": "8.0 to 9.3" - * } - * ``` - */ - type SupportedEnvironments = Record> -} - -declare module "@wdio/protocols" { - export default WDIOProtocols - export const WebDriverProtocol: WDIOProtocols.Protocol - export const MJsonWProtocol: WDIOProtocols.Protocol - export const JsonWProtocol: WDIOProtocols.Protocol - export const AppiumProtocol: WDIOProtocols.Protocol - export const ChromiumProtocol: WDIOProtocols.Protocol - export const SauceLabsProtocol: WDIOProtocols.Protocol - export const SeleniumProtocol: WDIOProtocols.Protocol -} diff --git a/packages/wdio-protocols/index.js b/packages/wdio-protocols/index.js deleted file mode 100644 index 9d004b795a2..00000000000 --- a/packages/wdio-protocols/index.js +++ /dev/null @@ -1,7 +0,0 @@ -exports.WebDriverProtocol = require('./protocols/webdriver.json') -exports.MJsonWProtocol = require('./protocols/mjsonwp.json') -exports.JsonWProtocol = require('./protocols/jsonwp.json') -exports.AppiumProtocol = require('./protocols/appium.json') -exports.ChromiumProtocol = require('./protocols/chromium.json') -exports.SauceLabsProtocol = require('./protocols/saucelabs.json') -exports.SeleniumProtocol = require('./protocols/selenium.json') diff --git a/packages/wdio-protocols/package.json b/packages/wdio-protocols/package.json index f648ac85160..2d642b53606 100644 --- a/packages/wdio-protocols/package.json +++ b/packages/wdio-protocols/package.json @@ -5,8 +5,8 @@ "author": "Christian Bromann ", "homepage": "https://github.com/webdriverio/webdriverio/tree/master/packages/wdio-protocols", "license": "MIT", - "main": "./index", - "types": "./index.d.ts", + "main": "./build/index", + "types": "./build/index.d.ts", "engines": { "node": ">=12.0.0" }, diff --git a/packages/wdio-protocols/protocols/jsonwp.json b/packages/wdio-protocols/protocols/jsonwp.json index 391a5a69909..b3d2ab78af8 100644 --- a/packages/wdio-protocols/protocols/jsonwp.json +++ b/packages/wdio-protocols/protocols/jsonwp.json @@ -325,7 +325,7 @@ "required": true }], "returns": { - "type": "String", + "type": "object", "name": "ELEMENT", "description": "A WebElement JSON object for the located element." } @@ -348,7 +348,7 @@ "required": true }], "returns": { - "type": "String[]", + "type": "object[]", "name": "ELEMENTS", "description": "A list of WebElement JSON objects for the located elements." } @@ -375,7 +375,7 @@ "required": true }], "returns": { - "type": "String", + "type": "object", "name": "ELEMENT", "description": "A WebElement JSON object for the located element." } @@ -402,7 +402,7 @@ "required": true }], "returns": { - "type": "String[]", + "type": "object[]", "name": "ELEMENTS", "description": "A list of WebElement JSON objects for the located elements." } diff --git a/packages/wdio-protocols/protocols/saucelabs.json b/packages/wdio-protocols/protocols/saucelabs.json index 9f24c119050..2e70b1693c2 100644 --- a/packages/wdio-protocols/protocols/saucelabs.json +++ b/packages/wdio-protocols/protocols/saucelabs.json @@ -254,7 +254,8 @@ "parameters": [{ "type": "boolean", "name": "restore", - "description": "Set to true if mock should be restored as well." + "description": "Set to true if mock should be restored as well.", + "required": false }] }, "POST": { diff --git a/packages/wdio-protocols/protocols/webdriver.json b/packages/wdio-protocols/protocols/webdriver.json index 02b823d278b..32462f82092 100644 --- a/packages/wdio-protocols/protocols/webdriver.json +++ b/packages/wdio-protocols/protocols/webdriver.json @@ -388,7 +388,7 @@ "required": true }], "returns": { - "type": "WebDriver.ElementReference[]", + "type": "object", "name": "element", "description": "A JSON representation of an element object." } @@ -411,7 +411,7 @@ "required": true }], "returns": { - "type": "WebDriver.ElementReference[]", + "type": "object[]", "name": "elements", "description": "A (possibly empty) JSON list of representations of an element object." } @@ -438,7 +438,7 @@ "required": true }], "returns": { - "type": "WebDriver.ElementReference", + "type": "object", "name": "element", "description": "A JSON representation of an element object." } @@ -465,7 +465,7 @@ "required": true }], "returns": { - "type": "WebDriver.ElementReference[]", + "type": "object[]", "name": "elements", "description": "A (possibly empty) JSON list of representations of an element object." } diff --git a/packages/wdio-protocols/src/index.ts b/packages/wdio-protocols/src/index.ts new file mode 100644 index 00000000000..4858bf00f19 --- /dev/null +++ b/packages/wdio-protocols/src/index.ts @@ -0,0 +1,58 @@ +import { Protocol } from './types' +import AppiumCommands from './commands/appium' +import ChromiumCommands from './commands/chromium' +import JSONWPCommands from './commands/jsonwp' +import MJSONWPCommands from './commands/mjsonwp' +import SauceLabsCommands from './commands/saucelabs' +import SeleniumCommands from './commands/selenium' +import WebDriverCommands from './commands/webdriver' + +const WebDriverProtocol: Protocol = require('../protocols/webdriver.json') +const MJsonWProtocol: Protocol = require('../protocols/mjsonwp.json') +const JsonWProtocol: Protocol = require('../protocols/jsonwp.json') +const AppiumProtocol: Protocol = require('../protocols/appium.json') +const ChromiumProtocol: Protocol = require('../protocols/chromium.json') +const SauceLabsProtocol: Protocol = require('../protocols/saucelabs.json') +const SeleniumProtocol: Protocol = require('../protocols/selenium.json') + +type WebDriverCommandsAsync = { + [K in keyof WebDriverCommands]: + (...args: Parameters) => Promise> +} +type AppiumCommandsAsync = { + [K in keyof AppiumCommands]: + (...args: Parameters) => Promise> +} +type ChromiumCommandsAsync = { + [K in keyof ChromiumCommands]: + (...args: Parameters) => Promise> +} +type JSONWPCommandsAsync = { + [K in keyof JSONWPCommands]: + (...args: Parameters) => Promise> +} +type MJSONWPCommandsAsync = { + [K in keyof MJSONWPCommands]: + (...args: Parameters) => Promise> +} +type SauceLabsCommandsAsync = { + [K in keyof SauceLabsCommands]: + (...args: Parameters) => Promise> +} +type SeleniumCommandsAsync = { + [K in keyof SeleniumCommands]: + (...args: Parameters) => Promise> +} + +export interface ProtocolCommands extends WebDriverCommands, Omit, AppiumCommands, ChromiumCommands, Omit, SauceLabsCommands, SeleniumCommands {} +export interface ProtocolCommandsAsync extends WebDriverCommandsAsync, Omit, AppiumCommandsAsync, ChromiumCommandsAsync, Omit, SauceLabsCommandsAsync, SeleniumCommandsAsync {} + +export * from './types' +export { + // protocols + WebDriverProtocol, MJsonWProtocol, JsonWProtocol, AppiumProtocol, + ChromiumProtocol, SauceLabsProtocol, SeleniumProtocol, + // commands + AppiumCommands, ChromiumCommands, JSONWPCommands, MJSONWPCommands, + SauceLabsCommands, SeleniumCommands, WebDriverCommands +} diff --git a/packages/wdio-protocols/src/types.ts b/packages/wdio-protocols/src/types.ts new file mode 100644 index 00000000000..99453866191 --- /dev/null +++ b/packages/wdio-protocols/src/types.ts @@ -0,0 +1,178 @@ +// object with no match +export interface ProtocolCommandResponse { + [key: string]: any; +} + +// webdriver.json +export interface SessionReturn extends /* DesiredCapabilities, */ ProtocolCommandResponse { } + +export interface StatusReturn extends ProtocolCommandResponse { + ready?: boolean, + message?: string, +} + +export type ElementReferenceId = 'element-6066-11e4-a52e-4f735466cecf' +export type ElementReference = Record + +export interface WindowHandle { + handle: string, + type: string +} + +export interface RectReturn { + x: number, + y: number, + width: number, + height: number +} + +// appium.json +export interface StringsReturn { + [key: string]: string +} + +export interface SettingsReturn extends ProtocolCommandResponse { + shouldUseCompactResponses?: boolean, + elementResponseAttributes?: string, + ignoreUnimportantViews?: boolean, + allowInvisibleElements?: boolean, + enableNotificationListener?: boolean, + actionAcknowledgmentTimeout?: number, + keyInjectionDelay?: number, + scrollAcknowledgmentTimeout?: number, + waitForIdleTimeout?: number, + waitForSelectorTimeout?: number, + normalizeTagNames?: boolean, + shutdownOnPowerDisconnect?: boolean, + mjpegServerScreenshotQuality?: number, + mjpegServerFramerate?: number, + screenshotQuality?: number, + mjpegScalingFactor?: number, +} + +export interface Timeouts { + implicit?: number, + pageLoad?: number, + script?: number +} + +export type SameSiteOptions = 'Lax' | 'Strict' +export interface Cookie { + /** + * The name of the cookie. + */ + name: string; + /** + * The cookie value. + */ + value: string; + /** + * The cookie path. Defaults to "/" if omitted when adding a cookie. + */ + path?: string; + /** + * The domain the cookie is visible to. Defaults to the current browsing context’s + * active document’s URL domain if omitted when adding a cookie. + */ + domain?: string; + /** + * Whether the cookie is a secure cookie. Defaults to false if omitted when adding + * a cookie. + */ + secure?: boolean; + /** + * Whether the cookie is an HTTP only cookie. Defaults to false if omitted when + * adding a cookie. + */ + httpOnly?: boolean; + /** + * When the cookie expires, specified in seconds since Unix Epoch. Must not be set if + * omitted when adding a cookie. + */ + expiry?: number; + /** + * Whether the cookie applies to a SameSite policy. Defaults to None if omitted when + * adding a cookie. Can be set to either "Lax" or "Strict". + */ + sameSite?: SameSiteOptions +} + +export type CommandPath = 'string' +export type CommandMethod = 'POST' | 'GET' | 'DELETE' +export type Protocol = Record> + +/** + * describes a command endpoint + */ +export interface CommandEndpoint { + /** + * command name + */ + command: string + /** + * command description + */ + description: string + /** + * link to specification reference + */ + ref: string + /** + * supported command parameters + */ + parameters: CommandParameters[] + /** + * variables within the command path (e.g. /:sessionId/element) + */ + variables?: CommandPathVariables[] + /** + * supported environments + */ + support?: SupportedEnvironments + /** + * set to true if command is only supported in Selenium Hub Node + */ + isHubCommand?: boolean, + /** + * information on return data + */ + returns?: CommandReturnObject +} + +export interface CommandReturnObject { + type: string + name: string + description: string +} + +export interface CommandPathVariables { + name: string + description: string + + /** + * the following are given for path variables, we should still define + * it as values are populated automatically + */ + required?: boolean + type?: string +} + +export interface CommandParameters { + name: string, + type: string, + description: string, + required: boolean +} + +export type Platform = 'ios' | 'android' +export type Environments = 'XCUITest' | 'UIAutomation' | 'UiAutomator' + +/** + * supported mobile environments, e.g. + * ``` + * "ios": { + * "UIAutomation": "8.0 to 9.3" + * } + * ``` + */ +export type SupportedEnvironments = Record> diff --git a/packages/wdio-protocols/tsconfig.json b/packages/wdio-protocols/tsconfig.json new file mode 100644 index 00000000000..3c464917ff2 --- /dev/null +++ b/packages/wdio-protocols/tsconfig.json @@ -0,0 +1,11 @@ +{ + "extends": "../../tsconfig", + "compilerOptions": { + "baseUrl": ".", + "outDir": "build", + "rootDir": "src" + }, + "include": [ + "src/**/*" + ] +} diff --git a/packages/wdio-repl/tsconfig.json b/packages/wdio-repl/tsconfig.json index a32652092d3..d660189bdb9 100644 --- a/packages/wdio-repl/tsconfig.json +++ b/packages/wdio-repl/tsconfig.json @@ -6,6 +6,7 @@ "rootDir": "./src" }, "include": [ - "src/**/*" + "src/**/*", + "../../@types" ] } diff --git a/packages/wdio-repl/tsconfig.prod.json b/packages/wdio-repl/tsconfig.prod.json index c7e2194a8b4..2a0e7e84140 100644 --- a/packages/wdio-repl/tsconfig.prod.json +++ b/packages/wdio-repl/tsconfig.prod.json @@ -6,6 +6,7 @@ "rootDir": "./src" }, "include": [ - "src/**/*" + "src/**/*", + "../../@types" ] } diff --git a/packages/wdio-reporter/package.json b/packages/wdio-reporter/package.json index 5c7e0f502fb..09f06b136bc 100644 --- a/packages/wdio-reporter/package.json +++ b/packages/wdio-reporter/package.json @@ -31,6 +31,7 @@ }, "dependencies": { "@types/cucumber": "^6.0.1", + "@wdio/types": "^6.10.6", "fs-extra": "^9.0.0" }, "types": "./build/index.d.ts" diff --git a/packages/wdio-reporter/src/index.ts b/packages/wdio-reporter/src/index.ts index 9a202c1c5c3..7957aeada56 100644 --- a/packages/wdio-reporter/src/index.ts +++ b/packages/wdio-reporter/src/index.ts @@ -1,7 +1,9 @@ import fs from 'fs' -import { WriteStream } from 'fs' +import type { WriteStream } from 'fs' import { createWriteStream, ensureDirSync } from 'fs-extra' import { EventEmitter } from 'events' +import type { Reporters } from '@wdio/types' + import { getErrorsFromEvent } from './utils' import SuiteStats, { Suite } from './stats/suite' import HookStats, { Hook } from './stats/hook' @@ -9,23 +11,10 @@ import TestStats, { Test } from './stats/test' import RunnerStats, { Runner } from './stats/runner' import { AfterCommandArgs, BeforeCommandArgs, CommandArgs, Tag } from './types' -export interface WDIOReporterBaseOptions { - outputDir?: string -} - -export interface WDIOReporterOptionsFromStdout extends WDIOReporterBaseOptions { - stdout: boolean - writeStream: WriteStream -} - -export interface WDIOReporterOptionsFromLogFile extends WDIOReporterBaseOptions { - logFile: string -} - -export type WDIOReporterOptions = WDIOReporterOptionsFromLogFile & WDIOReporterOptionsFromStdout +type CustomWriteStream = { write: (content: any) => boolean } export default class WDIOReporter extends EventEmitter { - outputStream: WriteStream + outputStream: WriteStream | CustomWriteStream failures = 0 suites: Record = {} hooks: Record = {} @@ -43,7 +32,7 @@ export default class WDIOReporter extends EventEmitter { runnerStat?: RunnerStats isContentPresent = false - constructor(public options: Partial) { + constructor(public options: Partial) { super() // ensure the report directory exists @@ -51,9 +40,9 @@ export default class WDIOReporter extends EventEmitter { ensureDirSync(this.options.outputDir) } - this.outputStream = (this.options as WDIOReporterOptionsFromStdout).stdout || !(this.options as WDIOReporterOptionsFromLogFile).logFile - ? (this.options as WDIOReporterOptionsFromStdout).writeStream - : createWriteStream((this.options as WDIOReporterOptionsFromLogFile).logFile) + this.outputStream = (this.options.stdout || !this.options.logFile) && this.options.writeStream + ? this.options.writeStream as CustomWriteStream + : createWriteStream(this.options.logFile!) let currentTest: TestStats @@ -182,7 +171,7 @@ export default class WDIOReporter extends EventEmitter { this.runnerStat.complete() this.onRunnerEnd(this.runnerStat) } - const logFile = (this.options as WDIOReporterOptionsFromLogFile).logFile + const logFile = (this.options as Reporters.Options).logFile if (!this.isContentPresent && logFile && fs.existsSync(logFile)) { fs.unlinkSync(logFile) } @@ -267,4 +256,7 @@ export default class WDIOReporter extends EventEmitter { onRunnerEnd(runnerStats: RunnerStats) { } } -export { SuiteStats, Tag, HookStats, TestStats, RunnerStats, BeforeCommandArgs, AfterCommandArgs, CommandArgs } +export { + SuiteStats, Tag, HookStats, TestStats, RunnerStats, BeforeCommandArgs, + AfterCommandArgs, CommandArgs +} diff --git a/packages/wdio-reporter/src/stats/runner.ts b/packages/wdio-reporter/src/stats/runner.ts index b48e4d4d337..cf0c44746fe 100644 --- a/packages/wdio-reporter/src/stats/runner.ts +++ b/packages/wdio-reporter/src/stats/runner.ts @@ -1,14 +1,15 @@ +import type { Capabilities, Options } from '@wdio/types' + import RunnableStats from './runnable' import { sanitizeCaps } from '../utils' -import { WDIOReporterOptions } from '..' export interface Runner { cid: string specs: string[] - config: WDIOReporterOptions + config: Options.Testrunner isMultiremote: boolean sessionId?: string - capabilities: WebDriver.DesiredCapabilities + capabilities: Capabilities.Capabilities retry?: number failures?: number retries?: number @@ -20,9 +21,9 @@ export interface Runner { */ export default class RunnerStats extends RunnableStats { cid: string - capabilities: WebDriver.DesiredCapabilities + capabilities: Capabilities.Capabilities sanitizedCapabilities: string - config: WDIOReporterOptions + config: Options.Testrunner specs: string[] sessionId?: string isMultiremote: boolean diff --git a/packages/wdio-reporter/src/stats/test.ts b/packages/wdio-reporter/src/stats/test.ts index ae48da96d32..cd7f862a7b9 100644 --- a/packages/wdio-reporter/src/stats/test.ts +++ b/packages/wdio-reporter/src/stats/test.ts @@ -90,5 +90,4 @@ export default class TestStats extends RunnableStats { this.error = errors[0] } } - } diff --git a/packages/wdio-reporter/src/utils.ts b/packages/wdio-reporter/src/utils.ts index 303081643cb..bfb87b287c5 100644 --- a/packages/wdio-reporter/src/utils.ts +++ b/packages/wdio-reporter/src/utils.ts @@ -1,3 +1,5 @@ +import { Capabilities } from '@wdio/types' + /** * replaces whitespaces with underscore and removes dots * @param {String} str variable to sanitize @@ -19,7 +21,7 @@ export function sanitizeString (str?: string) { * formats capability object into sanitized string for e.g.filenames * @param {Object} caps Selenium capabilities */ -export function sanitizeCaps (caps?: WebDriver.DesiredCapabilities) { +export function sanitizeCaps (caps?: Capabilities.DesiredCapabilities) { if (!caps) { return '' } diff --git a/packages/wdio-reporter/tsconfig.json b/packages/wdio-reporter/tsconfig.json index e0952f8f6bb..d660189bdb9 100644 --- a/packages/wdio-reporter/tsconfig.json +++ b/packages/wdio-reporter/tsconfig.json @@ -3,10 +3,10 @@ "compilerOptions": { "baseUrl": ".", "outDir": "./build", - "rootDir": "./src", - "types": ["webdriverio"] + "rootDir": "./src" }, "include": [ - "src/**/*" + "src/**/*", + "../../@types" ] } diff --git a/packages/wdio-reporter/tsconfig.prod.json b/packages/wdio-reporter/tsconfig.prod.json index 8ff66adac36..2a0e7e84140 100644 --- a/packages/wdio-reporter/tsconfig.prod.json +++ b/packages/wdio-reporter/tsconfig.prod.json @@ -3,10 +3,10 @@ "compilerOptions": { "baseUrl": ".", "outDir": "./build", - "rootDir": "./src", - "types": ["webdriverio"] + "rootDir": "./src" }, "include": [ - "src/**/*" + "src/**/*", + "../../@types" ] } diff --git a/packages/wdio-runner/package.json b/packages/wdio-runner/package.json index e0e954dfb4b..53bd7b14c78 100644 --- a/packages/wdio-runner/package.json +++ b/packages/wdio-runner/package.json @@ -24,6 +24,7 @@ "dependencies": { "@wdio/config": "6.12.1", "@wdio/logger": "6.10.10", + "@wdio/types": "^6.10.6", "@wdio/utils": "6.11.0", "deepmerge": "^4.0.0", "gaze": "^1.1.2", diff --git a/packages/wdio-runner/src/index.ts b/packages/wdio-runner/src/index.ts index c17f9d971da..fe1528cad7b 100644 --- a/packages/wdio-runner/src/index.ts +++ b/packages/wdio-runner/src/index.ts @@ -5,22 +5,36 @@ import { EventEmitter } from 'events' import logger from '@wdio/logger' import { initialiseWorkerService, initialisePlugin, executeHooksWithArgs } from '@wdio/utils' -import { ConfigParser, ConfigOptions, SingleConfigOption, Capability } from '@wdio/config' +import { ConfigParser } from '@wdio/config' +import type { Options, Capabilities } from '@wdio/types' +import type { Selector, Browser, MultiRemoteBrowser } from 'webdriverio' import BaseReporter from './reporter' import { initialiseInstance, filterLogTypes, getInstancesData } from './utils' const log = logger('@wdio/runner') -interface Args extends Partial { +declare global { + namespace NodeJS { + interface Global { + $: any + $$: any + browser: any + driver: any + } + } +} + +interface Args extends Partial { ignoredWorkerServices?: string[] + watch?: boolean } type RunParams = { cid: string args: Args specs: string[] - caps: Capability + caps: Capabilities.RemoteCapability configFile: string retries: number } @@ -28,26 +42,36 @@ type RunParams = { interface TestFramework { init: ( cid: string, - config: ConfigOptions, + config: Options.Testrunner, specs: string[], - capabilities: Capability, + capabilities: Capabilities.RemoteCapability, reporter: BaseReporter ) => TestFramework run (): number hasTests (): boolean } +type SingleCapability = { capabilities: Capabilities.RemoteCapability } +interface SingleConfigOption extends Omit, SingleCapability {} +type MultiRemoteCaps = Record + +// Todo(Christian): move to a central place +declare global { + var _HAS_FIBER_CONTEXT: boolean +} + export default class Runner extends EventEmitter { private _configParser = new ConfigParser() private _sigintWasCalled = false private _isMultiremote = false + private _specFileRetryAttempts = 0 private _reporter?: BaseReporter private _framework?: TestFramework - private _config?: ConfigOptions + private _config?: Options.Testrunner private _cid?: string private _specs?: string[] - private _caps?: Capability + private _caps?: Capabilities.RemoteCapability /** * run test suite @@ -78,8 +102,8 @@ export default class Runner extends EventEmitter { */ this._configParser.merge(args) - this._config = this._configParser.getConfig() as ConfigOptions - this._config.specFileRetryAttempts = (this._config.specFileRetries || 0) - (retries || 0) + this._config = this._configParser.getConfig() as Options.Testrunner + this._specFileRetryAttempts = (this._config.specFileRetries || 0) - (retries || 0) logger.setLogLevelsConfig(this._config.logLevels, this._config.logLevel) const isMultiremote = this._isMultiremote = !Array.isArray(this._configParser.getCapabilities()) @@ -97,8 +121,8 @@ export default class Runner extends EventEmitter { * run `beforeSession` command before framework and browser are initiated */ initialiseWorkerService( - this._config as WebdriverIO.Config, - caps as unknown as WebDriver.DesiredCapabilities, + this._config as Options.Testrunner, + caps as Capabilities.Capabilities, args.ignoredWorkerServices ).map(this._configParser.addService.bind(this._configParser)) await executeHooksWithArgs('beforeSession', this._config.beforeSession, [this._config, this._caps, this._specs]) @@ -123,7 +147,7 @@ export default class Runner extends EventEmitter { return this._shutdown(1, retries) } - this._reporter.caps = browser.capabilities as Capability + this._reporter.caps = browser.capabilities as Capabilities.RemoteCapability await executeHooksWithArgs('before', this._config.before, [this._caps, this._specs, browser]) @@ -140,7 +164,7 @@ export default class Runner extends EventEmitter { /** * initialisation successful, send start message */ - const multiRemoteBrowser = browser as WebdriverIO.MultiRemoteBrowserObject + const multiRemoteBrowser = browser as unknown as MultiRemoteBrowser<'async'> this._reporter.emit('runner:start', { cid, specs, @@ -148,14 +172,13 @@ export default class Runner extends EventEmitter { isMultiremote, sessionId: browser.sessionId, capabilities: isMultiremote - ? multiRemoteBrowser.instances.reduce((caps: WebdriverIO.MultiRemoteCapabilities, browserName) => { - // @ts-ignore loosly typed multiremotecaps + ? multiRemoteBrowser.instances.reduce((caps, browserName) => { caps[browserName] = multiRemoteBrowser[browserName].capabilities caps[browserName].sessionId = multiRemoteBrowser[browserName].sessionId return caps - }, {}) + }, {} as MultiRemoteCaps) : { ...browser.capabilities, sessionId: browser.sessionId }, - retry: this._config.specFileRetryAttempts + retry: this._specFileRetryAttempts }) /** @@ -176,7 +199,7 @@ export default class Runner extends EventEmitter { let failures = 0 try { failures = await this._framework.run() - await this._fetchDriverLogs(this._config, (caps as Required).excludeDriverLogs) + await this._fetchDriverLogs(this._config, (caps as Required).excludeDriverLogs) } catch (e) { log.error(e) this.emit('error', e) @@ -200,19 +223,19 @@ export default class Runner extends EventEmitter { * @param {Object} browserStub stubbed `browser` object with only capabilities, config and env flags * @return {Promise} resolves with browser object or null if session couldn't get established */ - async _initSession ( + private async _initSession ( config: SingleConfigOption, - caps: Capability, - browserStub?: WebdriverIO.BrowserObject | WebdriverIO.MultiRemoteBrowserObject + caps: Capabilities.RemoteCapability, + browserStub?: Browser<'async'> | MultiRemoteBrowser<'async'> ) { - const browser = await this._startSession(config, caps) + const browser = await this._startSession(config, caps) as Browser<'async'> // return null if session couldn't get established if (!browser) { return } // add flags declared by user to browser object if (browserStub) { - Object.entries(browserStub).forEach(([key, value]: [keyof WebdriverIO.BrowserObject, any]) => { + Object.entries(browserStub).forEach(([key, value]: [keyof Browser<'async'>, any]) => { if (typeof browser[key] === 'undefined') { // @ts-ignore allow to set value for undefined props browser[key] = value @@ -223,8 +246,8 @@ export default class Runner extends EventEmitter { /** * register global helper method to fetch elements */ - global.$ = (selector) => browser.$(selector) - global.$$ = (selector) => browser.$$(selector) + global.$ = (selector: Selector) => browser.$(selector) + global.$$ = (selector: Selector) => browser.$$(selector) /** * register command event @@ -251,11 +274,11 @@ export default class Runner extends EventEmitter { * @param {Object} caps desired capabilities of session * @return {Promise} resolves with browser object or null if session couldn't get established */ - async _startSession ( + private async _startSession ( config: SingleConfigOption, - caps: Capability + caps: Capabilities.RemoteCapability ) { - let browser: WebdriverIO.BrowserObject | WebdriverIO.MultiRemoteBrowserObject + let browser: Browser<'async'> | MultiRemoteBrowser<'async'> try { browser = global.browser = global.driver = await initialiseInstance(config, caps, this._isMultiremote) @@ -264,15 +287,16 @@ export default class Runner extends EventEmitter { return } - browser.config = config as WebdriverIO.Config + // @ts-expect-error + browser.config = config as Options.Testrunner return browser } /** * fetch logs provided by browser driver */ - async _fetchDriverLogs ( - config: ConfigOptions, + private async _fetchDriverLogs ( + config: Options.Testrunner, excludeDriverLogs: string[] ) { /** @@ -330,7 +354,7 @@ export default class Runner extends EventEmitter { return } - const stringLogs = logs.map((log) => JSON.stringify(log)).join('\n') + const stringLogs = logs.map((log: any) => JSON.stringify(log)).join('\n') return util.promisify(fs.writeFile)( path.join(config.outputDir!, `wdio-${this._cid}-${logType}.log`), stringLogs, @@ -342,7 +366,7 @@ export default class Runner extends EventEmitter { /** * kill worker session */ - async _shutdown ( + private async _shutdown ( failures: number, retries: number ) { @@ -368,7 +392,7 @@ export default class Runner extends EventEmitter { /** * make sure instance(s) exist and have `sessionId` */ - const multiremoteBrowser = global.browser as WebdriverIO.MultiRemoteBrowserObject + const multiremoteBrowser = global.browser as unknown as MultiRemoteBrowser<'async'> const hasSessionId = Boolean(global.browser) && (this._isMultiremote /** * every multiremote instance should exist and should have `sessionId` @@ -390,10 +414,10 @@ export default class Runner extends EventEmitter { /** * store capabilities for afterSession hook */ - const capabilities: WebDriver.DesiredCapabilities | Record = global.browser.capabilities || {} + const capabilities: Capabilities.Capabilities | Capabilities.W3CCapabilities | MultiRemoteCaps = global.browser.capabilities || {} if (this._isMultiremote) { multiremoteBrowser.instances.forEach((browserName) => { - (capabilities as Record)[browserName] = multiremoteBrowser[browserName].capabilities + (capabilities as MultiRemoteCaps)[browserName] = multiremoteBrowser[browserName].capabilities }) } diff --git a/packages/wdio-runner/src/reporter.ts b/packages/wdio-runner/src/reporter.ts index fe2d630c2cc..14ec73de6cd 100644 --- a/packages/wdio-runner/src/reporter.ts +++ b/packages/wdio-runner/src/reporter.ts @@ -1,50 +1,25 @@ import path from 'path' import logger from '@wdio/logger' import { initialisePlugin } from '@wdio/utils' -import type { ConfigOptions, Capability } from '@wdio/config' -import type { EventEmitter } from 'events' +import type { Options, Capabilities, Reporters } from '@wdio/types' import { sendFailureMessage } from './utils' const log = logger('@wdio/runner') -const NOOP = () => { } -const DEFAULT_SYNC_TIMEOUT = 5000 // 5s -const DEFAULT_SYNC_INTERVAL = 100 // 100ms - -interface Reporter extends EventEmitter { - isSynchronised: boolean -} - -type ReporterClass = (new (options: ReporterOptions) => Reporter) - -type ReporterOptions = { - logLevel?: string - writeStream?: { - write: (content: any) => boolean - } - logFile?: string - setLogFile: (cid: string, name: string) => string -} - /** * BaseReporter * responsible for initialising reporters for every testrun and propagating events * to all these reporters */ export default class BaseReporter { - private _reporterSyncInterval: number - private _reporterSyncTimeout: number - private _reporters: Reporter[] + private _reporters: Reporters.ReporterInstance[] constructor( - private _config: ConfigOptions, + private _config: Options.Testrunner, private _cid: string, - public caps: Capability + public caps: Capabilities.RemoteCapability ) { - this._reporterSyncInterval = this._config.reporterSyncInterval || DEFAULT_SYNC_INTERVAL - this._reporterSyncTimeout = this._config.reporterSyncTimeout || DEFAULT_SYNC_TIMEOUT - // ensure all properties are set before initializing the reporters this._reporters = this._config.reporters!.map(this.initReporter.bind(this)) } @@ -77,7 +52,7 @@ export default class BaseReporter { reporter[0] === name || typeof reporter[0] === 'function' && reporter[0].name === name ) - )) as { outputFileFormat: Function }[] + )) as { outputFileFormat?: Function }[] if (reporterOptions) { const fileformat = reporterOptions[1].outputFileFormat @@ -127,7 +102,7 @@ export default class BaseReporter { .filter((reporter) => !reporter.isSynchronised) .map((reporter) => reporter.constructor.name) - if ((Date.now() - startTime) > this._reporterSyncTimeout && unsyncedReporter.length) { + if ((Date.now() - startTime) > this._config.reporterSyncTimeout! && unsyncedReporter.length) { clearInterval(interval) return reject(new Error(`Some reporters are still unsynced: ${unsyncedReporter.join(', ')}`)) } @@ -142,19 +117,16 @@ export default class BaseReporter { log.info(`Wait for ${unsyncedReporter.length} reporter to synchronise`) // wait otherwise - }, this._reporterSyncInterval) + }, this._config.reporterSyncInterval) }) } /** * initialise reporters */ - initReporter (reporter: EventEmitter) { - let ReporterClass - let options: ReporterOptions = { - logLevel: this._config.logLevel, - setLogFile: NOOP as any - } + initReporter (reporter: Reporters.ReporterEntry) { + let ReporterClass: Reporters.ReporterClass + let options: Partial = {} /** * check if reporter has custom options @@ -180,9 +152,10 @@ export default class BaseReporter { * ``` */ if (typeof reporter === 'function') { - ReporterClass = reporter as ReporterClass - const customLogFile = options.setLogFile(this._cid, ReporterClass.name) - options.logFile = customLogFile || this.getLogFile(ReporterClass.name) + ReporterClass = reporter as Reporters.ReporterClass + options.logFile = options.setLogFile + ? options.setLogFile(this._cid, ReporterClass.name) + : this.getLogFile(ReporterClass.name) options.writeStream = this.getWriteStreamObject(ReporterClass.name) return new ReporterClass(options) } @@ -202,9 +175,10 @@ export default class BaseReporter { * ``` */ if (typeof reporter === 'string') { - ReporterClass = initialisePlugin(reporter, 'reporter').default as ReporterClass - const customLogFile = options.setLogFile(this._cid, reporter) - options.logFile = customLogFile || this.getLogFile(reporter) + ReporterClass = initialisePlugin(reporter, 'reporter').default as Reporters.ReporterClass + options.logFile = options.setLogFile + ? options.setLogFile(this._cid, reporter) + : this.getLogFile(reporter) options.writeStream = this.getWriteStreamObject(reporter) return new ReporterClass(options) } diff --git a/packages/wdio-runner/src/utils.ts b/packages/wdio-runner/src/utils.ts index 452b46916cc..4f0b00273fc 100644 --- a/packages/wdio-runner/src/utils.ts +++ b/packages/wdio-runner/src/utils.ts @@ -1,17 +1,19 @@ import merge from 'deepmerge' import logger from '@wdio/logger' -import { remote, multiremote, attach, HookFunctions } from 'webdriverio' +import { remote, multiremote, attach } from 'webdriverio' import { DEFAULTS } from 'webdriver' -import { DEFAULT_CONFIGS, ConfigOptions, Capability } from '@wdio/config' +import { DEFAULT_CONFIGS } from '@wdio/config' +import type { Options, Capabilities } from '@wdio/types' +import type { Browser, MultiRemoteBrowser } from 'webdriverio' const log = logger('@wdio/local-runner:utils') const MERGE_OPTIONS = { clone: false } const mochaAllHooks = ['"before all" hook', '"after all" hook'] -export interface ConfigWithSessionId extends Omit { - capabilities?: Capability - sessionId?: string +export interface ConfigWithSessionId extends Omit { + sessionId?: string, + capabilities: Capabilities.RemoteCapability } /** @@ -20,9 +22,9 @@ export interface ConfigWithSessionId extends Omit * @return {Object} sanitized caps */ export function sanitizeCaps ( - caps: Capability, + caps: Capabilities.RemoteCapability, filterOut?: boolean -): Capability { +): Omit | Partial { const defaultConfigsKeys = [ // WDIO config keys ...Object.keys(DEFAULT_CONFIGS()), @@ -30,14 +32,14 @@ export function sanitizeCaps ( ...Object.keys(DEFAULTS) ] - return Object.keys(caps).filter((key: keyof Capability) => ( + return Object.keys(caps).filter((key: keyof Capabilities.RemoteCapability) => ( /** * filter out all wdio config keys */ !defaultConfigsKeys.includes(key as string) === !filterOut )).reduce(( - obj: Capability, - key: keyof Capability + obj: Capabilities.RemoteCapability, + key: keyof Capabilities.RemoteCapability ) => { obj[key] = caps[key] return obj @@ -53,7 +55,7 @@ export function sanitizeCaps ( */ export async function initialiseInstance ( config: ConfigWithSessionId, - capabilities: Capability, + capabilities: Capabilities.RemoteCapability, isMultiremote?: boolean ) { /** @@ -67,18 +69,22 @@ export async function initialiseInstance ( if (!isMultiremote) { log.debug('init remote session') - const sessionConfig = { ...config, ...sanitizeCaps(capabilities, true) } as Omit - sessionConfig.capabilities = sanitizeCaps(capabilities) + const sessionConfig: Options.WebdriverIO = { + ...config, + ...sanitizeCaps(capabilities, true), + capabilities: sanitizeCaps(capabilities) + } return remote(sessionConfig) } - const options: WebdriverIO.MultiRemoteOptions = {} + const options: Record = {} log.debug('init multiremote session') + // @ts-expect-error ToDo(Christian): can be removed? delete config.capabilities for (let browserName of Object.keys(capabilities)) { options[browserName] = merge( config, - (capabilities as WebdriverIO.MultiRemoteCapabilities)[browserName], + (capabilities as Capabilities.MultiRemoteCapabilities)[browserName], MERGE_OPTIONS ) } @@ -158,14 +164,14 @@ type BrowserData = { * @return {object} */ export function getInstancesData ( - browser: WebdriverIO.BrowserObject | WebdriverIO.MultiRemoteBrowserObject, + browser: Browser<'async'> | MultiRemoteBrowser<'async'>, isMultiremote: boolean ) { if (!isMultiremote) { return } - const multiRemoteBrowser = browser as WebdriverIO.MultiRemoteBrowserObject + const multiRemoteBrowser = browser as MultiRemoteBrowser<'async'> const instances: Record> = {} multiRemoteBrowser.instances.forEach((browserName) => { const { protocol, hostname, port, path, queryParams } = multiRemoteBrowser[browserName].options diff --git a/packages/wdio-runner/tsconfig.json b/packages/wdio-runner/tsconfig.json index a32652092d3..d660189bdb9 100644 --- a/packages/wdio-runner/tsconfig.json +++ b/packages/wdio-runner/tsconfig.json @@ -6,6 +6,7 @@ "rootDir": "./src" }, "include": [ - "src/**/*" + "src/**/*", + "../../@types" ] } diff --git a/packages/wdio-runner/tsconfig.prod.json b/packages/wdio-runner/tsconfig.prod.json index c7e2194a8b4..2a0e7e84140 100644 --- a/packages/wdio-runner/tsconfig.prod.json +++ b/packages/wdio-runner/tsconfig.prod.json @@ -6,6 +6,7 @@ "rootDir": "./src" }, "include": [ - "src/**/*" + "src/**/*", + "../../@types" ] } diff --git a/packages/wdio-sauce-service/package.json b/packages/wdio-sauce-service/package.json index a2643f002f8..4d3cd7c8217 100644 --- a/packages/wdio-sauce-service/package.json +++ b/packages/wdio-sauce-service/package.json @@ -24,8 +24,10 @@ }, "dependencies": { "@wdio/logger": "6.10.10", + "@wdio/types": "^6.10.6", "@wdio/utils": "6.11.0", - "saucelabs": "^4.2.0" + "saucelabs": "^4.2.0", + "webdriverio": "^6.11.3" }, "peerDependencies": { "@wdio/cli": "^6.0.1" @@ -33,5 +35,5 @@ "publishConfig": { "access": "public" }, - "types": "sauce-service.d.ts" + "types": "./build/index.d.ts" } diff --git a/packages/wdio-sauce-service/src/index.ts b/packages/wdio-sauce-service/src/index.ts index f222c412b6e..daf7e5c58d5 100644 --- a/packages/wdio-sauce-service/src/index.ts +++ b/packages/wdio-sauce-service/src/index.ts @@ -1,5 +1,13 @@ import SauceLauncher from './launcher' import SauceService from './service' +import { SauceServiceConfig } from './types' export default SauceService export const launcher = SauceLauncher +export * from './types' + +declare global { + namespace WebdriverIO { + interface ServiceOption extends SauceServiceConfig {} + } +} diff --git a/packages/wdio-sauce-service/src/launcher.ts b/packages/wdio-sauce-service/src/launcher.ts index f5f1ded03dd..19bc4ef85d3 100644 --- a/packages/wdio-sauce-service/src/launcher.ts +++ b/packages/wdio-sauce-service/src/launcher.ts @@ -1,7 +1,8 @@ import { performance, PerformanceObserver } from 'perf_hooks' +import SauceLabs, { SauceLabsOptions, SauceConnectOptions, SauceConnectInstance } from 'saucelabs' import logger from '@wdio/logger' -import SauceLabs, { SauceLabsOptions, SauceConnectOptions, SauceConnectInstance } from 'saucelabs' +import type { Services, Capabilities, Options } from '@wdio/types' import { makeCapabilityFactory } from './utils' import type { SauceServiceConfig } from './types' @@ -14,14 +15,14 @@ const SC_RELAY_DEPCRECATION_WARNING = [ const MAX_SC_START_TRIALS = 3 const log = logger('@wdio/sauce-service') -export default class SauceLauncher { +export default class SauceLauncher implements Services.ServiceInstance { private _api: SauceLabs private _sauceConnectProcess?: SauceConnectInstance constructor ( private _options: SauceServiceConfig, - private _capabilities: WebDriver.Capabilities[] | WebdriverIO.MultiRemoteCapabilities, - private _config: WebdriverIO.Config + private _capabilities: unknown, + private _config: Options.Testrunner ) { this._api = new SauceLabs(this._config as unknown as SauceLabsOptions) } @@ -30,8 +31,8 @@ export default class SauceLauncher { * modify config and launch sauce connect */ async onPrepare ( - config: WebdriverIO.Config, - capabilities: WebDriver.Capabilities[] | WebdriverIO.MultiRemoteCapabilities + config: Options.Testrunner, + capabilities: Capabilities.RemoteCapabilities ) { if (!this._options.sauceConnect) { return @@ -67,11 +68,12 @@ export default class SauceLauncher { if (Array.isArray(capabilities)) { for (const capability of capabilities) { - prepareCapability(capability) + prepareCapability(capability as Capabilities.DesiredCapabilities) } } else { for (const browserName of Object.keys(capabilities)) { - prepareCapability((capabilities as WebdriverIO.MultiRemoteCapabilities)[browserName].capabilities) + const caps = capabilities[browserName].capabilities + prepareCapability((caps as Capabilities.W3CCapabilities).alwaysMatch || caps) } } diff --git a/packages/wdio-sauce-service/src/service.ts b/packages/wdio-sauce-service/src/service.ts index 9f77882e46b..de451005019 100644 --- a/packages/wdio-sauce-service/src/service.ts +++ b/packages/wdio-sauce-service/src/service.ts @@ -1,5 +1,7 @@ import SauceLabs, { SauceLabsOptions, Job } from 'saucelabs' import logger from '@wdio/logger' +import type { Services, Capabilities, Options, Frameworks } from '@wdio/types' +import type { Browser, MultiRemoteBrowser } from 'webdriverio' import { isUnifiedPlatform } from './utils' import { SauceServiceConfig } from './types' @@ -8,7 +10,7 @@ const jobDataProperties = ['name', 'tags', 'public', 'build', 'custom-data'] as const log = logger('@wdio/sauce-service') -export default class SauceService implements WebdriverIO.ServiceInstance { +export default class SauceService implements Services.ServiceInstance { private _testCnt = 0 private _maxErrorStackLength = 5 private _failures = 0 // counts failures between reloads @@ -17,14 +19,14 @@ export default class SauceService implements WebdriverIO.ServiceInstance { private _api: SauceLabs private _isRDC: boolean - private _browser?: WebdriverIO.BrowserObject | WebdriverIO.MultiRemoteBrowserObject + private _browser?: Browser<'async'> | MultiRemoteBrowser<'async'> private _isUP?: boolean private _suiteTitle?: string constructor ( private _options: SauceServiceConfig, - private _capabilities: WebDriver.Capabilities | WebDriver.DesiredCapabilities, - private _config: WebdriverIO.Config + private _capabilities: Capabilities.RemoteCapability, + private _config: Options.Testrunner ) { this._api = new SauceLabs(this._config as unknown as SauceLabsOptions) this._isRDC = 'testobject_api_key' in this._capabilities @@ -50,7 +52,7 @@ export default class SauceService implements WebdriverIO.ServiceInstance { } } - before (caps: WebDriver.Capabilities, specs: string[], browser: WebdriverIO.BrowserObject | WebdriverIO.MultiRemoteBrowserObject) { + before (caps: unknown, specs: string[], browser: Browser<'async'> | MultiRemoteBrowser<'async'>) { this._browser = browser // Ensure capabilities are not null in case of multiremote @@ -59,15 +61,15 @@ export default class SauceService implements WebdriverIO.ServiceInstance { // contains `simulator` or `emulator` it's an EMU/SIM session // `this._browser.capabilities` returns the process data from Sauce which is without // the postfix - const capabilities = (this._browser as WebdriverIO.Browser).requestedCapabilities || {} - this._isUP = isUnifiedPlatform(capabilities) + const capabilities = (this._browser as Browser<'async'>).requestedCapabilities || {} + this._isUP = isUnifiedPlatform(capabilities as Capabilities.Capabilities) } - beforeSuite (suite: any) { + beforeSuite (suite: Frameworks.Suite) { this._suiteTitle = suite.title } - beforeTest (test: any) { + beforeTest (test: Frameworks.Test) { /** * Date: 20200714 * Remark: Sauce Unified Platform doesn't support updating the context yet. @@ -83,7 +85,7 @@ export default class SauceService implements WebdriverIO.ServiceInstance { */ /* istanbul ignore if */ if (this._suiteTitle === 'Jasmine__TopLevel__Suite') { - this._suiteTitle = test.fullName.slice(0, test.fullName.indexOf(test.description) - 1) + this._suiteTitle = test.fullName.slice(0, test.fullName.indexOf(test.description || '') - 1) } if (this._browser && !this._isUP && !this._isJobNameSet) { @@ -101,16 +103,16 @@ export default class SauceService implements WebdriverIO.ServiceInstance { */ `${test.parent} - ${test.title}` ) - this._browser.execute('sauce:context=' + fullTitle) + ;(this._browser as Browser<'async'>).execute('sauce:context=' + fullTitle) } - afterSuite (suite: any) { + afterSuite (suite: Frameworks.Suite) { if (Object.prototype.hasOwnProperty.call(suite, 'error')) { ++this._failures } } - afterTest (test: any, context: any, results: any) { + afterTest (test: Frameworks.Test, context: unknown, results: Frameworks.TestResult) { /** * If the test failed push the stack to Sauce Labs in separate lines * This should not be done for UP because it's not supported yet and @@ -119,7 +121,7 @@ export default class SauceService implements WebdriverIO.ServiceInstance { const { error } = results if (error && !this._isUP){ const lines = error.stack.split(/\r?\n/).slice(0, this._maxErrorStackLength) - lines.forEach((line:string) => (this._browser as WebdriverIO.Browser).execute('sauce:context=' + line)) + lines.forEach((line:string) => this._browser.execute('sauce:context=' + line)) } /** @@ -135,7 +137,15 @@ export default class SauceService implements WebdriverIO.ServiceInstance { * don't bump failure number if test was retried and still failed * > Mocha only */ - if (test._retriedTest && !results.passed && test._currentRetry < test._retries) { + if ( + test._retriedTest && + !results.passed && + ( + typeof test._currentRetry === 'number' && + typeof test._retries === 'number' && + test._currentRetry < test._retries + ) + ) { return } @@ -147,7 +157,7 @@ export default class SauceService implements WebdriverIO.ServiceInstance { /** * For CucumberJS */ - beforeFeature (uri: any, feature: any) { + beforeFeature (uri: unknown, feature: { name: string }) { /** * Date: 20200714 * Remark: Sauce Unified Platform doesn't support updating the context yet. @@ -156,11 +166,11 @@ export default class SauceService implements WebdriverIO.ServiceInstance { return } - this._suiteTitle = feature.document.feature.name - this._browser.execute('sauce:context=Feature: ' + this._suiteTitle) + this._suiteTitle = feature.name + ;(this._browser as Browser<'async'>).execute('sauce:context=Feature: ' + this._suiteTitle) } - beforeScenario (uri: any, feature: any, scenario: any) { + beforeScenario (world: Frameworks.World) { /** * Date: 20200714 * Remark: Sauce Unified Platform doesn't support updating the context yet. @@ -169,12 +179,13 @@ export default class SauceService implements WebdriverIO.ServiceInstance { return } - const scenarioName = scenario.name - this._browser.execute('sauce:context=Scenario: ' + scenarioName) + const scenarioName = world.pickle.name || 'unknown scenario' + ;(this._browser as Browser<'async'>).execute('sauce:context=Scenario: ' + scenarioName) } - afterScenario(uri: any, feature: any, pickle: any, result: any) { - if (result.status === 'failed') { + afterScenario(world: Frameworks.World) { + // check if scenario has failed + if (world.result && world.result.status === 6) { ++this._failures } } @@ -193,7 +204,7 @@ export default class SauceService implements WebdriverIO.ServiceInstance { * set failures if user has bail option set in which case afterTest and * afterSuite aren't executed before after hook */ - if (this._browser.config.mochaOpts && this._browser.config.mochaOpts.bail && Boolean(result)) { + if (this._config.mochaOpts && this._config.mochaOpts.bail && Boolean(result)) { failures = 1 } @@ -203,7 +214,7 @@ export default class SauceService implements WebdriverIO.ServiceInstance { return this._isUP ? this.updateUP(failures) : this.updateJob(this._browser.sessionId, failures) } - const mulitremoteBrowser = this._browser as WebdriverIO.MultiRemoteBrowserObject + const mulitremoteBrowser = this._browser as MultiRemoteBrowser<'async'> return Promise.all(Object.keys(this._capabilities).map((browserName) => { log.info(`Update multiremote job for browser "${browserName}" and sessionId ${mulitremoteBrowser[browserName].sessionId}, ${status}`) return this._isUP ? this.updateUP(failures) : this.updateJob(mulitremoteBrowser[browserName].sessionId, failures, false, browserName) @@ -222,7 +233,7 @@ export default class SauceService implements WebdriverIO.ServiceInstance { return this.updateJob(oldSessionId, this._failures, true) } - const mulitremoteBrowser = this._browser as WebdriverIO.MultiRemoteBrowserObject + const mulitremoteBrowser = this._browser as MultiRemoteBrowser<'async'> const browserName = mulitremoteBrowser.instances.filter( (browserName) => mulitremoteBrowser[browserName].sessionId === newSessionId)[0] log.info(`Update (reloaded) multiremote job for browser "${browserName}" and sessionId ${oldSessionId}, ${status}`) @@ -262,7 +273,7 @@ export default class SauceService implements WebdriverIO.ServiceInstance { let testCnt = ++this._testCnt - const mulitremoteBrowser = this._browser as WebdriverIO.MultiRemoteBrowserObject + const mulitremoteBrowser = this._browser as MultiRemoteBrowser<'async'> if (this._browser && this._browser.isMultiremote) { testCnt = Math.ceil(testCnt / mulitremoteBrowser.instances.length) } @@ -270,7 +281,7 @@ export default class SauceService implements WebdriverIO.ServiceInstance { body.name += ` (${testCnt})` } - let caps = this._capabilities['sauce:options'] || this._capabilities as WebDriver.SauceLabsCapabilities + let caps = (this._capabilities as Capabilities.Capabilities)['sauce:options'] || this._capabilities as Capabilities.SauceLabsCapabilities for (let prop of jobDataProperties) { if (!caps[prop]) { diff --git a/packages/wdio-sauce-service/src/utils.ts b/packages/wdio-sauce-service/src/utils.ts index 781c76bdd0d..6b107137a65 100644 --- a/packages/wdio-sauce-service/src/utils.ts +++ b/packages/wdio-sauce-service/src/utils.ts @@ -1,4 +1,5 @@ import { isW3C } from '@wdio/utils' +import type { Capabilities } from '@wdio/types' import type { SauceServiceConfig } from './types' @@ -44,7 +45,7 @@ import type { SauceServiceConfig } from './types' * deviceContextId: '' * } */ -export function isUnifiedPlatform (caps: WebDriver.DesiredCapabilities){ +export function isUnifiedPlatform (caps: Capabilities.DesiredCapabilities){ const { 'appium:deviceName': appiumDeviceName = '', deviceName = '', platformName = '' } = caps const name = appiumDeviceName || deviceName @@ -57,7 +58,7 @@ export function isUnifiedPlatform (caps: WebDriver.DesiredCapabilities){ * @param {object} caps * @returns {boolean} */ -export function isEmuSim (caps: WebDriver.DesiredCapabilities){ +export function isEmuSim (caps: Capabilities.DesiredCapabilities){ const { 'appium:deviceName': appiumDeviceName = '', deviceName = '', platformName = '' } = caps const name = appiumDeviceName || deviceName @@ -71,7 +72,7 @@ export function isEmuSim (caps: WebDriver.DesiredCapabilities){ * @returns {function(object): void} - A function that mutates a single capability */ export function makeCapabilityFactory(tunnelIdentifier: string, options: any) { - return (capability: WebDriver.DesiredCapabilities) => { + return (capability: Capabilities.DesiredCapabilities) => { // If the capability appears to be using the legacy JSON Wire Protocol // we need to make sure the key 'sauce:options' is not present const isLegacy = Boolean( diff --git a/packages/wdio-sauce-service/tests/launcher.test.ts b/packages/wdio-sauce-service/tests/launcher.test.ts index 23085f1e8f6..8a1aebf3f6b 100644 --- a/packages/wdio-sauce-service/tests/launcher.test.ts +++ b/packages/wdio-sauce-service/tests/launcher.test.ts @@ -1,12 +1,13 @@ import logger from '@wdio/logger' import SauceLabs from 'saucelabs' +import type { Capabilities, Options } from '@wdio/types' import SauceServiceLauncher from '../src/launcher' import type { SauceServiceConfig } from '../src/types' jest.mock('saucelabs', () => { return class SauceLabsMock { - static instances = [] + static instances: SauceLabsMock[] = [] stop: Function startSauceConnect: Function @@ -29,11 +30,11 @@ test('onPrepare', async () => { tunnelIdentifier: 'my-tunnel' } } - const caps = [{}] as WebDriver.Capabilities[] + const caps = [{}] as Capabilities.DesiredCapabilities[] const config = { user: 'foobaruser', key: '12345' - } + } as Options.Testrunner const service = new SauceServiceLauncher(options, caps, config) expect(service['_sauceConnectProcess']).toBeUndefined() await service.onPrepare(config, caps) @@ -53,16 +54,16 @@ test('onPrepare w/o identifier', async () => { const options: SauceServiceConfig = { sauceConnect: true } - const caps = [{}] as WebDriver.Capabilities[] + const caps = [{}] as Capabilities.DesiredCapabilities[] const config = { user: 'foobaruser', key: '12345' - } + } as Options.Testrunner const service = new SauceServiceLauncher(options, caps, config) expect(service['_sauceConnectProcess']).toBeUndefined() await service.onPrepare(config, caps) - expect(caps[0]['sauce:options'].tunnelIdentifier).toContain('SC-tunnel-') + expect(caps[0]['sauce:options']?.tunnelIdentifier).toContain('SC-tunnel-') expect(service['_sauceConnectProcess']).not.toBeUndefined() // @ts-ignore mock feature @@ -76,11 +77,11 @@ test('onPrepare w/ SauceConnect w/o scRelay', async () => { const options: SauceServiceConfig = { sauceConnect: true } - const caps = [{}] as WebDriver.Capabilities[] - const config: WebdriverIO.Config = { + const caps = [{}] as Capabilities.DesiredCapabilities[] + const config = { user: 'foobaruser', key: '12345' - } + } as Options.Testrunner const service = new SauceServiceLauncher(options, caps, config) expect(service['_sauceConnectProcess']).toBeUndefined() await service.onPrepare(config, caps) @@ -97,8 +98,8 @@ test('onPrepare w/ SauceConnect w/ scRelay w/ default port', async () => { sauceConnect: true, sauceConnectOpts: { tunnelIdentifier: 'test123' } } - const caps = [{}] as WebDriver.Capabilities[] - const config = {} + const caps = [{}] as Capabilities.DesiredCapabilities[] + const config = {} as Options.Testrunner const service = new SauceServiceLauncher(options, caps, config) expect(service['_sauceConnectProcess']).toBeUndefined() await service.onPrepare(config, caps) @@ -120,12 +121,12 @@ test('onPrepare w/ SauceConnect w/ region EU', async () => { const options: SauceServiceConfig = { sauceConnect: true } - const caps = [{}] as WebDriver.Capabilities[] + const caps = [{}] as Capabilities.DesiredCapabilities[] const config = { user: 'foobaruser', key: '12345', region: 'eu' - } + } as Options.Testrunner const service = new SauceServiceLauncher(options, caps, config) expect(service['_sauceConnectProcess']).toBeUndefined() await service.onPrepare(config, caps) @@ -146,7 +147,7 @@ test('onPrepare multiremote', async () => { tunnelIdentifier: 'my-tunnel' } } - const caps: WebdriverIO.MultiRemoteCapabilities = { + const caps: Capabilities.MultiRemoteCapabilities = { browserA: { capabilities: { browserName: 'chrome' } }, @@ -157,7 +158,7 @@ test('onPrepare multiremote', async () => { const config = { user: 'foobaruser', key: '12345' - } + } as Options.Testrunner const service = new SauceServiceLauncher(options, caps, config) expect(service['_sauceConnectProcess']).toBeUndefined() await service.onPrepare(config, caps) @@ -192,11 +193,11 @@ test('onPrepare if sauceTunnel is not set', async () => { tunnelIdentifier: 'my-tunnel' } } - const caps = [{}] as WebDriver.Capabilities[] + const caps = [{}] as Capabilities.DesiredCapabilities[] const config = { user: 'foobaruser', key: '12345' - } + } as Options.Testrunner const service = new SauceServiceLauncher(options, caps, config) expect(service['_sauceConnectProcess']).toBeUndefined() await service.onPrepare(config, caps) @@ -216,7 +217,7 @@ test('onPrepare multiremote with tunnel identifier and with w3c caps ', async () tunnelIdentifier: 'my-tunnel' } } - const caps: WebdriverIO.MultiRemoteCapabilities = { + const caps: Capabilities.MultiRemoteCapabilities = { browserA: { capabilities: { browserName: 'chrome', @@ -238,7 +239,7 @@ test('onPrepare multiremote with tunnel identifier and with w3c caps ', async () const config = { user: 'foobaruser', key: '12345' - } + } as Options.Testrunner const service = new SauceServiceLauncher(options, caps, config) expect(service['_sauceConnectProcess']).toBeUndefined() await service.onPrepare(config, caps) @@ -270,7 +271,7 @@ test('onPrepare without tunnel identifier and without w3c caps ', async () => { const options: SauceServiceConfig = { sauceConnect: false } - const caps: WebDriver.DesiredCapabilities[] = [{ + const caps: Capabilities.DesiredCapabilities[] = [{ browserName: 'chrome' }, { browserName: 'firefox', @@ -279,7 +280,7 @@ test('onPrepare without tunnel identifier and without w3c caps ', async () => { const config = { user: 'foobaruser', key: '12345' - } + } as Options.Testrunner const service = new SauceServiceLauncher(options, caps, config) expect(service['_sauceConnectProcess']).toBeUndefined() await service.onPrepare(config, caps) @@ -297,7 +298,7 @@ test('onPrepare without tunnel identifier and with w3c caps ', async () => { const options: SauceServiceConfig = { sauceConnect: false } - const caps: WebDriver.Capabilities[] = [{ + const caps: Capabilities.DesiredCapabilities[] = [{ browserName: 'chrome', 'sauce:options': { commandTimeout: 600, @@ -312,7 +313,7 @@ test('onPrepare without tunnel identifier and with w3c caps ', async () => { const config = { user: 'foobaruser', key: '12345' - } + } as Options.Testrunner const service = new SauceServiceLauncher(options, caps, config) expect(service['_sauceConnectProcess']).toBeUndefined() await service.onPrepare(config, caps) @@ -341,7 +342,7 @@ test('onPrepare with tunnel identifier and with w3c caps ', async () => { tunnelIdentifier: 'my-tunnel' } } - const caps: WebDriver.Capabilities[] = [{ + const caps: Capabilities.DesiredCapabilities[] = [{ browserName: 'chrome', 'sauce:options': { commandTimeout: 600, @@ -356,7 +357,7 @@ test('onPrepare with tunnel identifier and with w3c caps ', async () => { const config = { user: 'foobaruser', key: '12345' - } + } as Options.Testrunner const service = new SauceServiceLauncher(options, caps, config) expect(service['_sauceConnectProcess']).toBeUndefined() await service.onPrepare(config, caps) @@ -392,7 +393,7 @@ test('onPrepare with tunnel identifier and without w3c caps ', async () => { tunnelIdentifier: 'my-tunnel' } } - const caps: WebDriver.DesiredCapabilities[] = [{ + const caps: Capabilities.DesiredCapabilities[] = [{ browserName: 'internet explorer', platform: 'Windows 7', tunnelIdentifier: 'fish' @@ -417,7 +418,7 @@ test('onPrepare with tunnel identifier and without w3c caps ', async () => { const config = { user: 'foobaruser', key: '12345' - } + } as Options.Testrunner const service = new SauceServiceLauncher(options, caps, config) expect(service['_sauceConnectProcess']).toBeUndefined() await service.onPrepare(config, caps) @@ -475,7 +476,7 @@ test('startTunnel fail twice and recover', async ()=> { tunnelIdentifier: 'my-tunnel' } } - const service = new SauceServiceLauncher(options, [{}], {}) + const service = new SauceServiceLauncher(options, [{}], {} as Options.Testrunner) ;(service['_api'].startSauceConnect as jest.Mock) .mockRejectedValueOnce(new Error('ENOENT')) .mockRejectedValueOnce(new Error('ENOENT')) @@ -492,7 +493,7 @@ test('startTunnel fail three and throws error', async ()=> { tunnelIdentifier: 'my-tunnel' } } - const service = new SauceServiceLauncher(options, [{}], {}) + const service = new SauceServiceLauncher(options, [{}], {} as Options.Testrunner) ;(service['_api'].startSauceConnect as jest.Mock) .mockRejectedValueOnce(new Error('ENOENT')) .mockRejectedValueOnce(new Error('ENOENT')) @@ -505,12 +506,12 @@ test('startTunnel fail three and throws error', async ()=> { }) test('onComplete', async () => { - const service = new SauceServiceLauncher({}, [], {}) + const service = new SauceServiceLauncher({}, [], {} as Options.Testrunner) expect(service.onComplete()).toBeUndefined() service['_sauceConnectProcess'] = { close: jest.fn() } as any service.onComplete() - expect(service['_sauceConnectProcess'].close).toBeCalled() + expect(service['_sauceConnectProcess']?.close).toBeCalled() }) afterEach(async () => { diff --git a/packages/wdio-sauce-service/tests/service.test.ts b/packages/wdio-sauce-service/tests/service.test.ts index 19a491870a4..4dbfd636eeb 100644 --- a/packages/wdio-sauce-service/tests/service.test.ts +++ b/packages/wdio-sauce-service/tests/service.test.ts @@ -1,41 +1,44 @@ import got from 'got' -import WebDriver from 'webdriver' +import type { MultiRemoteBrowser } from 'webdriverio' +import type { Capabilities, Options } from '@wdio/types' import SauceService from '../src' import { isUnifiedPlatform } from '../src/utils' const uri = '/some/uri' const featureObject = { - type: 'gherkin-document', - uri: '__tests__/features/passed.feature', - document: - { - type: 'GherkinDocument', - feature: - { - type: 'Feature', - tags: ['tag'], - location: ['Object'], - language: 'en', - keyword: 'Feature', - name: 'Create a feature', - description: ' the description', - children: [''], - }, - comments: [] - } + name: 'Create a feature' } -let browser:any +let browser: MultiRemoteBrowser beforeEach(() => { browser = { - config: {}, execute: jest.fn(), chromeA: { sessionId: 'sessionChromeA' }, chromeB: { sessionId: 'sessionChromeB' }, chromeC: { sessionId: 'sessionChromeC' }, instances: ['chromeA', 'chromeB', 'chromeC'], + } as any as MultiRemoteBrowser +}) + +test('constructor should set setJobNameInBeforeSuite', () => { + let service = new SauceService({}, {}, {} as any) + service['_browser'] = browser + expect(service['_options'].setJobNameInBeforeSuite).toBeFalsy() + + let options = { + setJobNameInBeforeSuite: false } + service = new SauceService(options, {}, {} as any) + service['_browser'] = browser + expect(service['_options'].setJobNameInBeforeSuite).toBeFalsy() + + options = { + setJobNameInBeforeSuite: true + } + service = new SauceService(options, {}, {} as any) + service['_browser'] = browser + expect(service['_options'].setJobNameInBeforeSuite).toBeTruthy() }) jest.mock('../src/utils', () => { @@ -45,21 +48,32 @@ jest.mock('../src/utils', () => { }) test('before should call isUnifiedPlatform', () => { - const service = new SauceService({}, {}, {}) + const service = new SauceService({}, {}, {} as any) service.before({}, [], browser) expect(isUnifiedPlatform).toBeCalledTimes(1) }) test('beforeSuite', () => { - const service = new SauceService({}, {}, {}) + const service = new SauceService({}, {}, {} as any) service['_browser'] = browser expect(service['_suiteTitle']).toBeUndefined() - service.beforeSuite({ title: 'foobar' }) + service.beforeSuite({ title: 'foobar' } as any) expect(service['_suiteTitle']).toBe('foobar') }) +test('beforeSuite should set job-name', () => { + const options = { + setJobNameInBeforeSuite: true + } + const service = new SauceService(options, {}, { user: 'foobar', key: '123' } as any) + service['_browser'] = browser + service.beforeSession() + service.beforeSuite({ title: 'foobar' } as any) + expect(browser.execute).toBeCalledWith('sauce:job-name=foobar') +}) + test('beforeSession should set to unknown creds if no sauce user and key are found', () => { - const config: WebdriverIO.Config = {} + const config: Options.Testrunner = { capabilities: [] } const service = new SauceService({}, {}, config) service['_browser'] = browser service.beforeSession() @@ -95,92 +109,92 @@ test('beforeTest not should set job-name when it has already been set', () => { }) test('beforeTest should set context for jasmine test', () => { - const service = new SauceService({}, {}, { user: 'foobar', key: '123' }) + const service = new SauceService({}, {}, { user: 'foobar', key: '123' } as any) service['_browser'] = browser service.beforeSession() service.beforeTest({ fullName: 'my test can do something', description: 'foobar' - }) + } as any) expect(browser.execute).toBeCalledWith('sauce:context=my test can do something') }) test('beforeTest should set context for mocha test', () => { - const service = new SauceService({}, {}, { user: 'foobar', key: '123' }) + const service = new SauceService({}, {}, { user: 'foobar', key: '123' } as any) service['_browser'] = browser service.beforeSession() service.beforeTest({ parent: 'foo', title: 'bar' - }) + } as any) expect(browser.execute).toBeCalledWith('sauce:context=foo - bar') }) test('beforeTest should not set context for RDC test', () => { // not for RDC since sauce:context is not available there - const rdcService = new SauceService({}, { testobject_api_key: 'foobar' }, {}) + const rdcService = new SauceService({}, { testobject_api_key: 'foobar' }, {} as any) rdcService['_browser'] = browser rdcService.beforeSession() rdcService.beforeTest({ fullTitle: 'my test can do something' - }) + } as any) expect(browser.execute).not.toBeCalled() }) test('beforeTest should not set context if user does not use sauce', () => { - const service = new SauceService({}, {}, {}) + const service = new SauceService({}, {}, {} as any) service['_browser'] = browser service.beforeSession() service.beforeTest({ fullTitle: 'my test can do something' - }) + } as any) expect(browser.execute).not.toBeCalled() }) test('afterSuite', () => { - const service = new SauceService({}, {}, {}) + const service = new SauceService({}, {}, {} as any) service['_browser'] = browser service.beforeSession() expect(service['_failures']).toBe(0) - service.afterSuite({}) + service.afterSuite({} as any) expect(service['_failures']).toBe(0) - service.afterSuite({ error: new Error('foobar') }) + service.afterSuite({ error: new Error('foobar') } as any) expect(service['_failures']).toBe(1) }) test('afterTest', () => { - const service = new SauceService({}, {}, {}) + const service = new SauceService({}, {}, {} as any) service['_browser'] = browser service.beforeSession() expect(service['_failures']).toBe(0) - service.afterTest({}, {}, { passed: true }) + service.afterTest({} as any, {}, { passed: true } as any) expect(service['_failures']).toBe(0) - service.afterTest({}, {}, { passed: false }) + service.afterTest({} as any, {}, { passed: false } as any) expect(service['_failures']).toBe(1) - service.afterTest({ _retriedTest: {} }, {}, { passed: true }) + service.afterTest({ _retriedTest: {} } as any, {}, { passed: true } as any) expect(service['_failures']).toBe(0) - service.afterTest({}, {}, { passed: false }) + service.afterTest({} as any, {}, { passed: false } as any) expect(service['_failures']).toBe(1) service.afterTest({ _retriedTest: {}, _currentRetry: 1, _retries: 2 - }, {}, { passed: false }) + } as any, {}, { passed: false } as any) expect(service['_failures']).toBe(1) service.afterTest({ _retriedTest: {}, _currentRetry: 2, _retries: 2 - }, {}, { passed: false }) + } as any, {}, { passed: false } as any) expect(service['_failures']).toBe(2) const stack = 'Error: Expected true to equal false.\n' + ' at \n' + @@ -233,7 +247,7 @@ test('afterTest', () => { }) test('beforeFeature should set context', () => { - const service = new SauceService({}, {}, { user: 'foobar', key: '123' }) + const service = new SauceService({}, {}, { user: 'foobar', key: '123' } as any) service['_browser'] = browser service.beforeSession() service.beforeFeature( uri, featureObject) @@ -241,7 +255,7 @@ test('beforeFeature should set context', () => { }) test('beforeFeature should not set context if RDC test', () => { - const rdcService = new SauceService({}, { testobject_api_key: 'foobar' }, {}) + const rdcService = new SauceService({}, { testobject_api_key: 'foobar' }, {} as any) rdcService['_browser'] = browser rdcService.beforeSession() rdcService.beforeFeature(uri, featureObject) @@ -249,7 +263,7 @@ test('beforeFeature should not set context if RDC test', () => { }) test('beforeFeature should not set context if no sauce user was applied', () => { - const service = new SauceService({}, {}, {}) + const service = new SauceService({}, {}, {} as any) service['_browser'] = browser service.beforeSession() service.beforeFeature(uri, featureObject) @@ -257,57 +271,59 @@ test('beforeFeature should not set context if no sauce user was applied', () => }) test('afterScenario', () => { - const service = new SauceService({}, {}, {}) + const service = new SauceService({}, {}, {} as any) service['_browser'] = browser service.beforeSession() expect(service['_failures']).toBe(0) - service.afterScenario(uri, {}, {}, { status: 'passed' }) + service.afterScenario({ result: { status: 1 } }) expect(service['_failures']).toBe(0) - service.afterScenario(uri, {}, {}, { status: 'failed' }) + service.afterScenario({ result: { status: 6 } }) expect(service['_failures']).toBe(1) - service.afterScenario(uri, {}, {}, { status: 'passed' }) + service.afterScenario({ result: { status: 1 } }) expect(service['_failures']).toBe(1) - service.afterScenario(uri, {}, {}, { status: 'failed' }) + service.afterScenario({ result: { status: 6 } }) expect(service['_failures']).toBe(2) }) test('beforeScenario should set context', () => { - const service = new SauceService({}, {}, { user: 'foobar', key: '123' }) + const service = new SauceService({}, {}, { user: 'foobar', key: '123' } as any) service['_browser'] = browser service.beforeSession() - service.beforeScenario(uri, featureObject, { name: 'foobar' }) + service.beforeScenario({ pickle: { name: 'foobar' } }) expect(browser.execute).toBeCalledWith('sauce:context=Scenario: foobar') }) test('beforeScenario should not set context if RDC test', () => { - const rdcService = new SauceService({}, { testobject_api_key: 'foobar' }, {}) + const rdcService = new SauceService({}, { testobject_api_key: 'foobar' }, {} as any) rdcService['_browser'] = browser rdcService.beforeSession() - rdcService.beforeScenario(uri, featureObject, { name: 'foobar' }) + rdcService.beforeScenario({ pickle: { name: 'foobar' } }) expect(browser.execute).not.toBeCalledWith('sauce:context=Scenario: foobar') }) test('beforeScenario should not set context if no sauce user was applied', () => { - const service = new SauceService({}, {}, {}) + const service = new SauceService({}, {}, {} as any) service['_browser'] = browser service.beforeSession() - service.beforeScenario(uri, featureObject, { name: 'foobar' }) + service.beforeScenario({ pickle: { name: 'foobar' } }) expect(browser.execute).not.toBeCalledWith('sauce:context=Scenario: foobar') }) test('after', () => { - const service = new SauceService({}, {}, { user: 'foobar', key: '123' }) + const service = new SauceService({}, {}, { user: 'foobar', key: '123' } as any) service['_browser'] = browser service.beforeSession() service['_failures'] = 5 service.updateJob = jest.fn() + // @ts-expect-error browser.isMultiremote = false + // @ts-expect-error browser.sessionId = 'foobar' service.after({}) @@ -315,13 +331,15 @@ test('after', () => { }) test('after for RDC', () => { - const service = new SauceService({}, { testobject_api_key: '1' }, {}) + const service = new SauceService({}, { testobject_api_key: '1' }, {} as any) service['_browser'] = browser service.beforeSession() service['_failures'] = 5 service.updateJob = jest.fn() + // @ts-expect-error browser.isMultiremote = false + // @ts-expect-error browser.sessionId = 'foobar' service.after({}) @@ -329,7 +347,7 @@ test('after for RDC', () => { }) test('after for UP', () => { - const service = new SauceService({}, {}, {}) + const service = new SauceService({}, {}, {} as any) service['_browser'] = browser browser.capabilities = {} service.updateUP = jest.fn() @@ -337,6 +355,7 @@ test('after for UP', () => { service['_isUP'] = true service['_failures'] = 5 + // @ts-expect-error browser.isMultiremote = false service.after({}) @@ -344,7 +363,7 @@ test('after for UP', () => { }) test('after for UP with multi remote', () => { - const caps: WebdriverIO.MultiRemoteCapabilities = { + const caps: Capabilities.MultiRemoteCapabilities = { chromeA: { capabilities: {} }, chromeB: { capabilities: {} }, chromeC: { capabilities: {} } @@ -352,7 +371,7 @@ test('after for UP with multi remote', () => { const service = new SauceService( {}, caps, - { user: 'foobar', key: '123' } + { user: 'foobar', key: '123' } as any ) service['_browser'] = browser service.beforeSession() @@ -363,6 +382,7 @@ test('after for UP with multi remote', () => { service['_failures'] = 0 browser.isMultiremote = true + // @ts-expect-error browser.sessionId = 'foobar' service.after({}) @@ -370,28 +390,31 @@ test('after for UP with multi remote', () => { }) test('after with bail set', () => { - const service = new SauceService({}, {}, { user: 'foobar', key: '123' }) + const service = new SauceService({}, {}, { user: 'foobar', key: '123', mochaOpts: { bail: 1 } } as any) service['_browser'] = browser service.beforeSession() service['_failures'] = 5 service.updateJob = jest.fn() + // @ts-expect-error browser.isMultiremote = false + // @ts-expect-error browser.sessionId = 'foobar' - browser.config = { mochaOpts: { bail: 1 } } service.after(1) expect(service.updateJob).toBeCalledWith('foobar', 1) }) test('beforeScenario should not set context if no sauce user was applied', () => { - const service = new SauceService({}, {}, {}) + const service = new SauceService({}, {}, {} as any) service['_browser'] = browser service.beforeSession() service['_failures'] = 5 service.updateJob = jest.fn() + // @ts-expect-error browser.isMultiremote = false + // @ts-expect-error browser.sessionId = 'foobar' service.after({}) @@ -399,18 +422,19 @@ test('beforeScenario should not set context if no sauce user was applied', () => }) test('after in multiremote', () => { - const caps: WebdriverIO.MultiRemoteCapabilities = { + const caps: Capabilities.MultiRemoteCapabilities = { chromeA: { capabilities: {} }, chromeB: { capabilities: {} }, chromeC: { capabilities: {} } } - const service = new SauceService({}, caps, { user: 'foobar', key: '123' }) + const service = new SauceService({}, caps, { user: 'foobar', key: '123' } as any) service['_browser'] = browser service.beforeSession() service['_failures'] = 5 service.updateJob = jest.fn() browser.isMultiremote = true + // @ts-expect-error browser.sessionId = 'foobar' service.after({}) @@ -420,13 +444,15 @@ test('after in multiremote', () => { }) test('onReload', () => { - const service = new SauceService({}, {}, { user: 'foobar', key: '123' }) + const service = new SauceService({}, {}, { user: 'foobar', key: '123' } as any) service['_browser'] = browser service.beforeSession() service['_failures'] = 5 service.updateJob = jest.fn() + // @ts-expect-error browser.isMultiremote = false + // @ts-expect-error browser.sessionId = 'foobar' service.onReload('oldbar', 'newbar') @@ -434,13 +460,15 @@ test('onReload', () => { }) test('onReload with RDC', () => { - const service = new SauceService({}, { testobject_api_key: '1' }, {}) + const service = new SauceService({}, { testobject_api_key: '1' }, {} as any) service['_browser'] = browser service.beforeSession() service['_failures'] = 0 service.updateJob = jest.fn() + // @ts-expect-error browser.isMultiremote = false + // @ts-expect-error browser.sessionId = 'foobar' service.onReload('oldbar', 'newbar') @@ -448,13 +476,15 @@ test('onReload with RDC', () => { }) test('onReload should not set context if no sauce user was applied', () => { - const service = new SauceService({}, {}, {}) + const service = new SauceService({}, {}, {} as any) service['_browser'] = browser service.beforeSession() service['_failures'] = 5 service.updateJob = jest.fn() + // @ts-expect-error browser.isMultiremote = false + // @ts-expect-error browser.sessionId = 'foobar' service.onReload('oldbar', 'newbar') @@ -462,18 +492,19 @@ test('onReload should not set context if no sauce user was applied', () => { }) test('after in multiremote', () => { - const caps: WebdriverIO.MultiRemoteCapabilities = { + const caps: Capabilities.MultiRemoteCapabilities = { chromeA: { capabilities: {} }, chromeB: { capabilities: {} }, chromeC: { capabilities: {} } } - const service = new SauceService({}, caps, { user: 'foobar', key: '123' }) + const service = new SauceService({}, caps, { user: 'foobar', key: '123' } as any) service['_browser'] = browser service.beforeSession() service['_failures'] = 5 service.updateJob = jest.fn() browser.isMultiremote = true + // @ts-expect-error browser.sessionId = 'foobar' browser.chromeB.sessionId = 'newSessionChromeB' service.onReload('sessionChromeB', 'newSessionChromeB') @@ -482,7 +513,7 @@ test('after in multiremote', () => { }) test('updateJob for VMs', () => { - const service = new SauceService({}, {}, { user: 'foobar', key: '123' }) + const service = new SauceService({}, {}, { user: 'foobar', key: '123' } as any) service['_browser'] = browser service.beforeSession() service['_suiteTitle'] = 'my test' @@ -496,7 +527,7 @@ test('updateJob for VMs', () => { }) test('updateJob for RDC', () => { - const service = new SauceService({}, { testobject_api_key: '1' }, {}) + const service = new SauceService({}, { testobject_api_key: '1' }, {} as any) service['_browser'] = browser service.beforeSession() @@ -515,7 +546,7 @@ test('getBody', () => { public: true, build: 'foobuild', 'custom-data': { some: 'data' } - }, {}) + }, {} as any) service['_browser'] = browser service['_suiteTitle'] = 'jojo' service.beforeSession() @@ -529,7 +560,7 @@ test('getBody', () => { passed: true }) - service['_capabilities'] = {} as WebDriver.Capabilities + service['_capabilities'] = {} as Capabilities.Capabilities expect(service.getBody(1)).toEqual({ passed: false }) @@ -560,7 +591,7 @@ test('getBody', () => { public: true, build: 'foobuild', 'custom-data': { some: 'data' } - }, {}) + }, {} as any) service['_browser'] = browser service['_suiteTitle'] = 'jojo' service.beforeSession() @@ -601,7 +632,7 @@ test('getBody', () => { test('getBody with name Capability (JSON WP)', () => { const service = new SauceService({}, { name: 'bizarre' - }, {}) + }, {} as any) service['_browser'] = browser service['_suiteTitle'] = 'jojo' service.beforeSession() @@ -635,7 +666,7 @@ test('getBody with name Capability (W3C)', () => { 'sauce:options': { name: 'bizarre' } - }, {}) + }, {} as any) service['_browser'] = browser service['_suiteTitle'] = 'jojo' service.beforeSession() @@ -670,12 +701,13 @@ test('getBody without multiremote', () => { public: true, build: 'foobuild', 'custom-data': { some: 'data' } - }, {}) + }, {} as any) service['_browser'] = browser service['_suiteTitle'] = 'jojo' service.beforeSession() service['_testCnt'] = 3 + // @ts-expect-error browser.isMultiremote = false expect(service.getBody(0, true)).toEqual({ name: 'jojo (4)', @@ -688,20 +720,21 @@ test('getBody without multiremote', () => { }) test('updateUP should set job status to false', () => { - const service = new SauceService({}, {}, {}) + const service = new SauceService({}, {}, {} as any) service['_browser'] = browser service.updateUP(1) expect(browser.execute).toBeCalledWith('sauce:job-result=false') }) test('updateUP should set job status to false', () => { - const service = new SauceService({}, {}, {}) + const service = new SauceService({}, {}, {} as any) service['_browser'] = browser service.updateUP(0) expect(browser.execute).toBeCalledWith('sauce:job-result=true') }) afterEach(() => { + // @ts-expect-error browser = undefined ;(got.put as jest.Mock).mockClear() }) diff --git a/packages/wdio-sauce-service/tsconfig.json b/packages/wdio-sauce-service/tsconfig.json index ce83193f4c2..d660189bdb9 100644 --- a/packages/wdio-sauce-service/tsconfig.json +++ b/packages/wdio-sauce-service/tsconfig.json @@ -3,10 +3,10 @@ "compilerOptions": { "baseUrl": ".", "outDir": "./build", - "rootDir": "./src", - "types": ["webdriverio", "@wdio/logger", "jest", "@wdio/mocha-framework", "@wdio/jasmine-framework", "@wdio/cucumber-framework"] + "rootDir": "./src" }, "include": [ - "src/**/*" + "src/**/*", + "../../@types" ] } diff --git a/packages/wdio-sauce-service/tsconfig.prod.json b/packages/wdio-sauce-service/tsconfig.prod.json index 7c722d8346c..2a0e7e84140 100644 --- a/packages/wdio-sauce-service/tsconfig.prod.json +++ b/packages/wdio-sauce-service/tsconfig.prod.json @@ -3,10 +3,10 @@ "compilerOptions": { "baseUrl": ".", "outDir": "./build", - "rootDir": "./src", - "types": ["webdriverio", "@wdio/logger", "jest", "@wdio/mocha-framework", "@wdio/jasmine-framework", "@wdio/cucumber-framework"] + "rootDir": "./src" }, "include": [ - "src/**/*" + "src/**/*", + "../../@types" ] } diff --git a/packages/wdio-selenium-standalone-service/launcher.ts b/packages/wdio-selenium-standalone-service/launcher.ts deleted file mode 100644 index e5f4b302d27..00000000000 --- a/packages/wdio-selenium-standalone-service/launcher.ts +++ /dev/null @@ -1,2 +0,0 @@ -const SeleniumLauncher = require('./build/launcher').default -module.exports = new SeleniumLauncher() diff --git a/packages/wdio-selenium-standalone-service/package.json b/packages/wdio-selenium-standalone-service/package.json index d445b2952f7..ff8f1c794e7 100644 --- a/packages/wdio-selenium-standalone-service/package.json +++ b/packages/wdio-selenium-standalone-service/package.json @@ -31,6 +31,7 @@ "@types/selenium-standalone": "^6.15.2", "@wdio/config": "6.12.1", "@wdio/logger": "6.10.10", + "@wdio/types": "^6.10.6", "fs-extra": "^9.0.1", "selenium-standalone": "^6.22.1" }, @@ -40,5 +41,5 @@ "publishConfig": { "access": "public" }, - "types": "selenium-standalone-service.d.ts" + "types": "./build/index.d.ts" } diff --git a/packages/wdio-selenium-standalone-service/src/index.ts b/packages/wdio-selenium-standalone-service/src/index.ts index a8ce14b6e1a..73799ae0540 100644 --- a/packages/wdio-selenium-standalone-service/src/index.ts +++ b/packages/wdio-selenium-standalone-service/src/index.ts @@ -1,6 +1,18 @@ /* istanbul ignore file */ import SeleniumStandaloneLauncher from './launcher' +import { SeleniumStandaloneOptions } from './types' export default class SeleniumStandaloneService {} export const launcher = SeleniumStandaloneLauncher +export * from './types' + +declare global { + namespace WebdriverIO { + /** + * property `args` also exists in Appium + * ToDo(Christian): fine a better way to extend service options + */ + interface ServiceOption extends Omit {} + } +} diff --git a/packages/wdio-selenium-standalone-service/src/launcher.ts b/packages/wdio-selenium-standalone-service/src/launcher.ts index 7ec00d14cf3..1d2d69678f4 100644 --- a/packages/wdio-selenium-standalone-service/src/launcher.ts +++ b/packages/wdio-selenium-standalone-service/src/launcher.ts @@ -1,11 +1,13 @@ import logger from '@wdio/logger' import { isCloudCapability } from '@wdio/config' +import type { Capabilities, Options, Services } from '@wdio/types' import { promisify } from 'util' import fs from 'fs-extra' -import SeleniumStandalone from 'selenium-standalone' +import * as SeleniumStandalone from 'selenium-standalone' import { getFilePath } from './utils' +import type { SeleniumStandaloneOptions } from './types' const DEFAULT_LOG_FILENAME = 'wdio-selenium-standalone.log' const log = logger('@wdio/selenium-standalone-service') @@ -28,8 +30,6 @@ type BrowserDrivers = { } export default class SeleniumStandaloneLauncher { - capabilities: WebDriver.DesiredCapabilities[] | WebdriverIO.MultiRemoteCapabilities - logPath?: string args: SeleniumStartArgs installArgs: SeleniumInstallArgs skipSeleniumInstall: boolean @@ -44,17 +44,15 @@ export default class SeleniumStandaloneLauncher { } constructor( - options: WebdriverIO.ServiceOption, - capabilities: WebDriver.DesiredCapabilities[] | WebdriverIO.MultiRemoteCapabilities, - config: WebdriverIO.Config + private _options: SeleniumStandaloneOptions, + private _capabilities: Capabilities.RemoteCapabilities, + private _config: Omit ) { - this.capabilities = capabilities - this.logPath = options.logPath || config.outputDir - this.skipSeleniumInstall = Boolean(options.skipSeleniumInstall) + this.skipSeleniumInstall = Boolean(this._options.skipSeleniumInstall) // simplified mode - if (this.isSimplifiedMode(options)) { - this.args = Object.entries(options.drivers as BrowserDrivers).reduce((acc, [browserDriver, version]) => { + if (this.isSimplifiedMode(this._options)) { + this.args = Object.entries(this._options.drivers as BrowserDrivers).reduce((acc, [browserDriver, version]) => { if (typeof version === 'string') { acc.drivers![browserDriver] = { version } } else if (version === true) { @@ -64,12 +62,12 @@ export default class SeleniumStandaloneLauncher { }, { drivers: {} } as SeleniumStartArgs) this.installArgs = { ...this.args } as SeleniumInstallArgs } else { - this.args = options.args || {} - this.installArgs = options.installArgs || {} + this.args = this._options.args || {} + this.installArgs = this._options.installArgs || {} } } - async onPrepare(config: WebdriverIO.Config): Promise { + async onPrepare(config: Options.Testrunner): Promise { this.watchMode = Boolean(config.watch) if (!this.skipSeleniumInstall) { @@ -81,11 +79,15 @@ export default class SeleniumStandaloneLauncher { * update capability connection options to connect * to standalone server */ - const capabilities = Array.isArray(this.capabilities) ? this.capabilities : Object.values(this.capabilities) - capabilities.forEach( - (cap: WebDriver.DesiredCapabilities | WebdriverIO.MultiRemoteBrowserOptions) => - !isCloudCapability(cap) && Object.assign(cap, DEFAULT_CONNECTION, { ...cap }) - ) + const capabilities = (Array.isArray(this._capabilities) ? this._capabilities : Object.values(this._capabilities)) + for (const capability of capabilities) { + const cap = (capability as Options.WebDriver).capabilities || capability + const c = (cap as Capabilities.W3CCapabilities).alwaysMatch || cap + + if (!isCloudCapability(c)) { + Object.assign(c, DEFAULT_CONNECTION, { ...c }) + } + } /** * start Selenium Standalone server @@ -93,7 +95,7 @@ export default class SeleniumStandaloneLauncher { const start: (opts: SeleniumStandalone.StartOpts) => Promise = promisify(SeleniumStandalone.start) this.process = await start(this.args) - if (typeof this.logPath === 'string') { + if (typeof this._config.outputDir === 'string') { this._redirectLogStream() } @@ -112,7 +114,7 @@ export default class SeleniumStandaloneLauncher { } _redirectLogStream(): void { - const logFile = getFilePath(this.logPath!, DEFAULT_LOG_FILENAME) + const logFile = getFilePath(this._config.outputDir!, DEFAULT_LOG_FILENAME) // ensure file & directory exists fs.ensureFileSync(logFile) @@ -129,7 +131,7 @@ export default class SeleniumStandaloneLauncher { } } - private isSimplifiedMode(options: WebdriverIO.ServiceOption) { + private isSimplifiedMode(options: Services.ServiceOption) { return options.drivers && Object.keys(options.drivers).length > 0 } } diff --git a/packages/wdio-selenium-standalone-service/selenium-standalone-service.d.ts b/packages/wdio-selenium-standalone-service/src/types.ts similarity index 73% rename from packages/wdio-selenium-standalone-service/selenium-standalone-service.d.ts rename to packages/wdio-selenium-standalone-service/src/types.ts index 018376868ad..6c449b8aeda 100644 --- a/packages/wdio-selenium-standalone-service/selenium-standalone-service.d.ts +++ b/packages/wdio-selenium-standalone-service/src/types.ts @@ -1,28 +1,26 @@ -declare module WebdriverIO { - interface ServiceOption extends SeleniumStandaloneOptions {} -} +import type { InstallOpts, StartOpts } from 'selenium-standalone' -interface SeleniumStandaloneOptions { +export interface SeleniumStandaloneOptions { /** * Path where all logs from the Selenium server should be stored. */ - logs?: string; + logs?: string /** * Map of arguments for the Selenium server, passed directly to `Selenium.start()`. * Please note that latest drivers have to be installed, see `seleniumInstallArgs`. */ - installArgs?: Partial; + installArgs?: Partial /** * Map of arguments for the Selenium server, passed directly to `Selenium.install()`. * * By default, versions will be installed based on what is set in the selenium-standalone * package. The defaults can be overridden by specifying the versions. */ - args?: Partial; + args?: Partial /** * Boolean for skipping `selenium-standalone` server install. */ - skipSeleniumInstall?: boolean; + skipSeleniumInstall?: boolean /** * simplified way to pass browser driver versions */ @@ -32,5 +30,5 @@ interface SeleniumStandaloneOptions { chromiumedge?: string | boolean ie?: string | boolean edge?: string | boolean - }; + } } diff --git a/packages/wdio-selenium-standalone-service/tests/__mocks__/selenium-standalone.ts b/packages/wdio-selenium-standalone-service/tests/__mocks__/selenium-standalone.ts index 417393c037a..46c11fdab6e 100644 --- a/packages/wdio-selenium-standalone-service/tests/__mocks__/selenium-standalone.ts +++ b/packages/wdio-selenium-standalone-service/tests/__mocks__/selenium-standalone.ts @@ -1,4 +1,4 @@ -export default { +export = { install : jest.fn((seleniumInstallArgs, cb) => { cb() }), @@ -10,4 +10,4 @@ export default { stderr : { pipe: jest.fn().mockReturnValue({ pipe : jest.fn() }) } }) }) -} \ No newline at end of file +} diff --git a/packages/wdio-selenium-standalone-service/tests/launcher.test.ts b/packages/wdio-selenium-standalone-service/tests/launcher.test.ts index 951ebbf4a8f..baa1c2511d9 100644 --- a/packages/wdio-selenium-standalone-service/tests/launcher.test.ts +++ b/packages/wdio-selenium-standalone-service/tests/launcher.test.ts @@ -3,6 +3,8 @@ import fs from 'fs-extra' import Selenium from 'selenium-standalone' import SeleniumStandaloneLauncher from '../src/launcher' +const expect = global.expect as any as jest.Expect + jest.mock('fs-extra', () => ({ createWriteStream: jest.fn(), ensureFileSync: jest.fn(), @@ -22,11 +24,10 @@ describe('Selenium standalone launcher', () => { installArgs: { drivers: { chrome: {} } }, } const capabilities: any = [{ port: 1234 }] - const launcher = new SeleniumStandaloneLauncher(options, capabilities, {}) + const launcher = new SeleniumStandaloneLauncher(options, capabilities, {} as any) launcher._redirectLogStream = jest.fn() await launcher.onPrepare({ watch: true } as never) - expect(launcher.logPath).toBe(options.logPath) expect(launcher.installArgs).toBe(options.installArgs) expect(launcher.args).toBe(options.args) expect(launcher.skipSeleniumInstall).toBe(false) @@ -47,7 +48,7 @@ describe('Selenium standalone launcher', () => { browserA: { port: 1234 }, browserB: { port: 4321 } } - const launcher = new SeleniumStandaloneLauncher(options, capabilities, {}) + const launcher = new SeleniumStandaloneLauncher(options, capabilities, {} as any) launcher._redirectLogStream = jest.fn() await launcher.onPrepare({ watch: true } as never) expect(capabilities.browserA.protocol).toBe('http') @@ -70,7 +71,7 @@ describe('Selenium standalone launcher', () => { browserA: { port: 1234 }, browserB: { port: 4321, capabilities: { 'bstack:options': {} } } } - const launcher = new SeleniumStandaloneLauncher(options, capabilities, {}) + const launcher = new SeleniumStandaloneLauncher(options, capabilities, {} as any) launcher._redirectLogStream = jest.fn() await launcher.onPrepare({ watch: true } as never) expect(capabilities.browserA.protocol).toBe('http') @@ -106,9 +107,9 @@ describe('Selenium standalone launcher', () => { } } } - const launcher = new SeleniumStandaloneLauncher(options, [], {}) + const launcher = new SeleniumStandaloneLauncher(options, [], { outputDir: '/foo/bar' }) launcher._redirectLogStream = jest.fn() - await launcher.onPrepare({}) + await launcher.onPrepare({} as any) expect((Selenium.install as jest.Mock).mock.calls[0][0]).toBe(options.installArgs) expect((Selenium.start as jest.Mock).mock.calls[0][0]).toBe(options.args) @@ -128,9 +129,9 @@ describe('Selenium standalone launcher', () => { }, skipSeleniumInstall: true } - const launcher = new SeleniumStandaloneLauncher(options, [], {}) + const launcher = new SeleniumStandaloneLauncher(options, [], { outputDir: '/foo/bar' }) launcher._redirectLogStream = jest.fn() - await launcher.onPrepare({}) + await launcher.onPrepare({} as any) expect(Selenium.install).not.toBeCalled() expect((Selenium.start as jest.Mock).mock.calls[0][0]).toBe(options.args) @@ -141,9 +142,9 @@ describe('Selenium standalone launcher', () => { const launcher = new SeleniumStandaloneLauncher({ installArgs: {}, args: {}, - }, [], {}) + }, [], {} as any) launcher._redirectLogStream = jest.fn() - await launcher.onPrepare({}) + await launcher.onPrepare({} as any) expect(launcher._redirectLogStream).not.toBeCalled() }) @@ -154,7 +155,7 @@ describe('Selenium standalone launcher', () => { const launcher = new SeleniumStandaloneLauncher({ installArgs: {}, args: {} - }, [], {}) + }, [], {} as any) launcher._redirectLogStream = jest.fn() await launcher.onPrepare({ watch: true } as never) @@ -169,7 +170,7 @@ describe('Selenium standalone launcher', () => { drivers: { chrome: 'latest', firefox: '0.28.0', ie: true, chromiumedge: '87.0.637.0', edge: false }, } const capabilities: any = [{ port: 1234 }] - const launcher = new SeleniumStandaloneLauncher(options, capabilities, {}) + const launcher = new SeleniumStandaloneLauncher(options, capabilities, {} as any) const seleniumArgs = { drivers: { chrome: { version: 'latest' }, firefox: { version: '0.28.0' }, ie: {}, chromiumedge: { version: '87.0.637.0' } } } expect(launcher.args).toEqual(seleniumArgs) @@ -182,7 +183,7 @@ describe('Selenium standalone launcher', () => { drivers: { chrome: 'latest' }, } const capabilities: any = [{ port: 1234 }] - const launcher = new SeleniumStandaloneLauncher(options, capabilities, {}) + const launcher = new SeleniumStandaloneLauncher(options, capabilities, {} as any) const seleniumArgs = { drivers: { chrome: { version: 'latest' } } } expect(launcher.args).toEqual(seleniumArgs) @@ -195,7 +196,7 @@ describe('Selenium standalone launcher', () => { drivers: {}, } const capabilities: any = [{ port: 1234 }] - const launcher = new SeleniumStandaloneLauncher(options, capabilities, {}) + const launcher = new SeleniumStandaloneLauncher(options, capabilities, {} as any) const seleniumArgs = {} expect(launcher.args).toEqual(seleniumArgs) @@ -208,16 +209,16 @@ describe('Selenium standalone launcher', () => { const launcher = new SeleniumStandaloneLauncher({ installArgs: {}, args: {}, - }, [], {}) + }, [], {} as any) launcher._redirectLogStream = jest.fn() - await launcher.onPrepare({}) + await launcher.onPrepare({} as any) launcher.onComplete() expect(launcher.process.kill).toBeCalled() }) test('should not call process.kill', () => { - const launcher = new SeleniumStandaloneLauncher({}, [], {}) + const launcher = new SeleniumStandaloneLauncher({}, [], {} as any) launcher.onComplete() expect(launcher.process).toBeFalsy() @@ -227,7 +228,7 @@ describe('Selenium standalone launcher', () => { const launcher = new SeleniumStandaloneLauncher({ installArgs: {}, args: {} - }, [], {}) + }, [], {} as any) launcher._redirectLogStream = jest.fn() await launcher.onPrepare({ watch: true } as never) launcher.onComplete() @@ -239,11 +240,10 @@ describe('Selenium standalone launcher', () => { describe('_redirectLogStream', () => { test('should write output to file', async () => { const launcher = new SeleniumStandaloneLauncher({ - logPath: './', installArgs: {}, args: {}, - }, [], {}) - await launcher.onPrepare({}) + }, [], { outputDir: './' } as any) + await launcher.onPrepare({} as any) expect((fs.createWriteStream as jest.Mock).mock.calls[0][0]) .toBe(path.join(process.cwd(), 'wdio-selenium-standalone.log')) diff --git a/packages/wdio-selenium-standalone-service/tsconfig.json b/packages/wdio-selenium-standalone-service/tsconfig.json index 41f53514877..d660189bdb9 100644 --- a/packages/wdio-selenium-standalone-service/tsconfig.json +++ b/packages/wdio-selenium-standalone-service/tsconfig.json @@ -3,10 +3,10 @@ "compilerOptions": { "baseUrl": ".", "outDir": "./build", - "rootDir": "./src", - "types": ["webdriverio", "webdriver"] + "rootDir": "./src" }, "include": [ - "src/**/*" + "src/**/*", + "../../@types" ] } diff --git a/packages/wdio-selenium-standalone-service/tsconfig.prod.json b/packages/wdio-selenium-standalone-service/tsconfig.prod.json index 94d56e06ead..2a0e7e84140 100644 --- a/packages/wdio-selenium-standalone-service/tsconfig.prod.json +++ b/packages/wdio-selenium-standalone-service/tsconfig.prod.json @@ -3,10 +3,10 @@ "compilerOptions": { "baseUrl": ".", "outDir": "./build", - "rootDir": "./src", - "types": ["webdriverio", "webdriver"] + "rootDir": "./src" }, "include": [ - "src/**/*" + "src/**/*", + "../../@types" ] } diff --git a/packages/wdio-shared-store-service/package.json b/packages/wdio-shared-store-service/package.json index 4affe53da12..aceb936f58c 100644 --- a/packages/wdio-shared-store-service/package.json +++ b/packages/wdio-shared-store-service/package.json @@ -25,11 +25,13 @@ "dependencies": { "@polka/parse": "^1.0.0-next.0", "@wdio/logger": "6.10.10", + "@wdio/types": "^6.10.6", "got": "^11.0.2", - "polka": "^0.5.2" + "polka": "^0.5.2", + "webdriverio": "^6.11.3" }, "publishConfig": { "access": "public" }, - "types": "shared-store-service.d.ts" + "types": "./build/index.d.ts" } diff --git a/packages/wdio-shared-store-service/shared-store-service.d.ts b/packages/wdio-shared-store-service/shared-store-service.d.ts deleted file mode 100644 index c4082a8bd6b..00000000000 --- a/packages/wdio-shared-store-service/shared-store-service.d.ts +++ /dev/null @@ -1,8 +0,0 @@ -declare namespace WebdriverIO { - interface Browser { - sharedStore: { - get: (key: string) => JsonPrimitive | JsonCompatible; - set: (key: string, value: JsonPrimitive | JsonCompatible) => void; - } - } -} diff --git a/packages/wdio-shared-store-service/src/client.ts b/packages/wdio-shared-store-service/src/client.ts index 9032daced36..b36baf5c56b 100644 --- a/packages/wdio-shared-store-service/src/client.ts +++ b/packages/wdio-shared-store-service/src/client.ts @@ -1,6 +1,8 @@ import got, { Response } from 'got' import logger from '@wdio/logger' +import type { JsonCompatible, JsonPrimitive, JsonObject, JsonArray } from '@wdio/types' + const log = logger('@wdio/shared-store-service') let baseUrl: string @@ -11,9 +13,9 @@ export const setPort = (port: string) => { baseUrl = `http://localhost:${port}` * @param {string} key * @returns {*} */ -export const getValue = async (key: string) => { +export const getValue = async (key: string): Promise => { const res = await got.post(`${baseUrl}/get`, { json: { key }, responseType: 'json' }).catch(errHandler) - return (res && res.body) ? (res.body as WebdriverIO.JsonObject).value : undefined + return (res && res.body) ? (res.body as JsonObject).value : undefined } /** @@ -21,7 +23,7 @@ export const getValue = async (key: string) => { * @param {string} key * @param {*} value `store[key]` value (plain object) */ -export const setValue = async (key: string, value: WebdriverIO.JsonCompatible | WebdriverIO.JsonPrimitive) => { +export const setValue = async (key: string, value: JsonCompatible | JsonPrimitive) => { await got.post(`${baseUrl}/set`, { json: { key, value } }).catch(errHandler) } diff --git a/packages/wdio-shared-store-service/src/index.ts b/packages/wdio-shared-store-service/src/index.ts index a0ff53cc254..d6e58173906 100644 --- a/packages/wdio-shared-store-service/src/index.ts +++ b/packages/wdio-shared-store-service/src/index.ts @@ -1,5 +1,26 @@ +import type { JsonPrimitive, JsonCompatible } from '@wdio/types' + import SharedStoreLauncher from './launcher' import SharedStoreService from './service' export default SharedStoreService export const launcher = SharedStoreLauncher + +export interface BrowserExtension { + sharedStore: { + get: (key: string) => JsonPrimitive | JsonCompatible; + set: (key: string, value: JsonPrimitive | JsonCompatible) => void; + } +} + +declare global { + namespace WebdriverIOAsync { + interface Browser extends BrowserExtension { } + interface MultiRemoteBrowser extends BrowserExtension { } + } + + namespace WebdriverIOSync { + interface Browser extends BrowserExtension { } + interface MultiRemoteBrowser extends BrowserExtension { } + } +} diff --git a/packages/wdio-shared-store-service/src/server.ts b/packages/wdio-shared-store-service/src/server.ts index c8510a5ca8e..4875aa9ac0a 100644 --- a/packages/wdio-shared-store-service/src/server.ts +++ b/packages/wdio-shared-store-service/src/server.ts @@ -2,7 +2,9 @@ import type { AddressInfo } from 'net' import polka from 'polka' import { json } from '@polka/parse' -const store: WebdriverIO.JsonObject = {} +import type { JsonCompatible, JsonPrimitive, JsonObject } from '@wdio/types' + +const store: JsonObject = {} const validateBody: NextFn = (req, res, next) => { if (!req.path.endsWith('/get') && !req.path.endsWith('/set')) { @@ -26,7 +28,7 @@ const app = polka() res.end(JSON.stringify({ value: store[req.body.key as string] })) }) .post('/set', (req, res) => { - store[req.body.key as string] = req.body.value as WebdriverIO.JsonCompatible | WebdriverIO.JsonPrimitive + store[req.body.key as string] = req.body.value as JsonCompatible | JsonPrimitive return res.end() }) diff --git a/packages/wdio-shared-store-service/src/service.ts b/packages/wdio-shared-store-service/src/service.ts index 600e717cadb..24870a31432 100644 --- a/packages/wdio-shared-store-service/src/service.ts +++ b/packages/wdio-shared-store-service/src/service.ts @@ -1,8 +1,17 @@ +import { Browser } from 'webdriverio' +import { BrowserExtension } from './index' import { readFile, getPidPath } from './utils' import { getValue, setValue, setPort } from './client' -export default class SharedStoreService implements WebdriverIO.HookFunctions { - private _browser?: WebdriverIO.BrowserObject | WebdriverIO.MultiRemoteBrowserObject +import type { JsonCompatible, JsonPrimitive, Services } from '@wdio/types' + +/** + * ToDo(Christian): make this public accessible + */ +interface ServiceBrowser extends Browser<'async'>, BrowserExtension { } + +export default class SharedStoreService implements Services.ServiceInstance { + private _browser?: ServiceBrowser async beforeSession () { /** @@ -14,9 +23,9 @@ export default class SharedStoreService implements WebdriverIO.HookFunctions { } before ( - caps: WebDriver.Capabilities, - specs: string[], - browser: WebdriverIO.BrowserObject | WebdriverIO.MultiRemoteBrowserObject + caps: unknown, + specs: unknown, + browser: ServiceBrowser ) { this._browser = browser const sharedStore = Object.create({}, { @@ -26,7 +35,7 @@ export default class SharedStoreService implements WebdriverIO.HookFunctions { set: { value: ( key: string, - value: WebdriverIO.JsonCompatible | WebdriverIO.JsonPrimitive + value: JsonCompatible | JsonPrimitive ) => this._browser?.call(() => setValue(key, value)) } }) diff --git a/packages/wdio-shared-store-service/tsconfig.json b/packages/wdio-shared-store-service/tsconfig.json index e0952f8f6bb..d660189bdb9 100644 --- a/packages/wdio-shared-store-service/tsconfig.json +++ b/packages/wdio-shared-store-service/tsconfig.json @@ -3,10 +3,10 @@ "compilerOptions": { "baseUrl": ".", "outDir": "./build", - "rootDir": "./src", - "types": ["webdriverio"] + "rootDir": "./src" }, "include": [ - "src/**/*" + "src/**/*", + "../../@types" ] } diff --git a/packages/wdio-shared-store-service/tsconfig.prod.json b/packages/wdio-shared-store-service/tsconfig.prod.json index 8ff66adac36..2a0e7e84140 100644 --- a/packages/wdio-shared-store-service/tsconfig.prod.json +++ b/packages/wdio-shared-store-service/tsconfig.prod.json @@ -3,10 +3,10 @@ "compilerOptions": { "baseUrl": ".", "outDir": "./build", - "rootDir": "./src", - "types": ["webdriverio"] + "rootDir": "./src" }, "include": [ - "src/**/*" + "src/**/*", + "../../@types" ] } diff --git a/packages/wdio-smoke-test-reporter/package.json b/packages/wdio-smoke-test-reporter/package.json index 01bca5ccd0e..94dbe0b9160 100644 --- a/packages/wdio-smoke-test-reporter/package.json +++ b/packages/wdio-smoke-test-reporter/package.json @@ -21,5 +21,6 @@ }, "publishConfig": { "access": "public" - } + }, + "types": "./build/index.d.ts" } diff --git a/packages/wdio-smoke-test-service/package.json b/packages/wdio-smoke-test-service/package.json index ec220efdb61..6abf11ec06e 100644 --- a/packages/wdio-smoke-test-service/package.json +++ b/packages/wdio-smoke-test-service/package.json @@ -19,5 +19,6 @@ }, "publishConfig": { "access": "public" - } + }, + "types": "./build/index.d.ts" } diff --git a/packages/wdio-spec-reporter/tsconfig.json b/packages/wdio-spec-reporter/tsconfig.json index a32652092d3..d660189bdb9 100644 --- a/packages/wdio-spec-reporter/tsconfig.json +++ b/packages/wdio-spec-reporter/tsconfig.json @@ -6,6 +6,7 @@ "rootDir": "./src" }, "include": [ - "src/**/*" + "src/**/*", + "../../@types" ] } diff --git a/packages/wdio-spec-reporter/tsconfig.prod.json b/packages/wdio-spec-reporter/tsconfig.prod.json index c7e2194a8b4..2a0e7e84140 100644 --- a/packages/wdio-spec-reporter/tsconfig.prod.json +++ b/packages/wdio-spec-reporter/tsconfig.prod.json @@ -6,6 +6,7 @@ "rootDir": "./src" }, "include": [ - "src/**/*" + "src/**/*", + "../../@types" ] } diff --git a/packages/wdio-static-server-service/package.json b/packages/wdio-static-server-service/package.json index ec2d8ec9170..916cac773ff 100644 --- a/packages/wdio-static-server-service/package.json +++ b/packages/wdio-static-server-service/package.json @@ -31,6 +31,7 @@ "@types/fs-extra": "^9.0.1", "@types/morgan": "^1.9.1", "@wdio/logger": "6.10.10", + "@wdio/types": "^6.10.6", "express": "^4.14.0", "fs-extra": "^9.0.0", "morgan": "^1.7.0" @@ -38,5 +39,5 @@ "publishConfig": { "access": "public" }, - "types": "static-server-service.d.ts" + "types": "./build/index.d.ts" } diff --git a/packages/wdio-static-server-service/src/index.ts b/packages/wdio-static-server-service/src/index.ts index 4d2b1411f31..db0ab9ffcdc 100644 --- a/packages/wdio-static-server-service/src/index.ts +++ b/packages/wdio-static-server-service/src/index.ts @@ -1,6 +1,14 @@ /* istanbul ignore file */ import StaticServerLauncher from './launcher' +import { StaticServerOptions } from './types' export default class StaticServerService { } export const launcher = StaticServerLauncher +export * from './types' + +declare global { + namespace WebdriverIO { + interface ServiceOption extends StaticServerOptions {} + } +} diff --git a/packages/wdio-static-server-service/src/launcher.ts b/packages/wdio-static-server-service/src/launcher.ts index b5e5ea021ee..fd0d428dc9b 100644 --- a/packages/wdio-static-server-service/src/launcher.ts +++ b/packages/wdio-static-server-service/src/launcher.ts @@ -4,47 +4,50 @@ import express from 'express' import fs from 'fs-extra' import morgan from 'morgan' import logger from '@wdio/logger' +import type { Services } from '@wdio/types' + import { FolderOption, MiddleWareOption } from './types' const log = logger('@wdio/static-server-service') const DEFAULT_LOG_NAME = 'wdio-static-server-service.log' -export default class StaticServerLauncher { - folders: FolderOption[] | null - port: number - middleware: MiddleWareOption[] - server!: express.Express +export default class StaticServerLauncher implements Services.ServiceInstance { + private _folders: FolderOption[] | null + private _port: number + private _middleware: MiddleWareOption[] + private _server?: express.Express + constructor({ folders, port = 4567, middleware = [] }: { folders?: FolderOption[] | FolderOption, port?: number, middleware?: MiddleWareOption[] }) { - this.folders = folders ? Array.isArray(folders) ? folders : [folders] : null - this.port = port - this.middleware = middleware + this._folders = folders ? Array.isArray(folders) ? folders : [folders] : null + this._port = port + this._middleware = middleware } async onPrepare({ outputDir }: { outputDir?: string }) { - if (!this.folders) { + if (!this._folders) { return } - this.server = express() + this._server = express() if (outputDir) { const file = join(outputDir, DEFAULT_LOG_NAME) fs.createFileSync(file) const stream = fs.createWriteStream(file) - this.server.use(morgan('tiny', { stream })) + this._server.use(morgan('tiny', { stream })) } - this.folders.forEach((folder: FolderOption) => { + this._folders.forEach((folder: FolderOption) => { log.info('Mounting folder `%s` at `%s`', resolve(folder.path), folder.mount) - this.server.use(folder.mount, express.static(folder.path)) + this._server!.use(folder.mount, express.static(folder.path)) }) - this.middleware.forEach( - (ware: MiddleWareOption) => this.server.use(ware.mount, ware.middleware as unknown as express.Application)) + this._middleware.forEach( + (ware: MiddleWareOption) => this._server!.use(ware.mount, ware.middleware as unknown as express.Application)) - const listen = <(port: number) => Promise> promisify(this.server.listen.bind(this.server)) - await listen(this.port) - log.info(`Static server running at http://localhost:${this.port}`) + const listen = <(port: number) => Promise> promisify(this._server.listen.bind(this._server)) + await listen(this._port) + log.info(`Static server running at http://localhost:${this._port}`) } } diff --git a/packages/wdio-static-server-service/static-server-service.d.ts b/packages/wdio-static-server-service/static-server-service.d.ts deleted file mode 100644 index a5cdb02a3d8..00000000000 --- a/packages/wdio-static-server-service/static-server-service.d.ts +++ /dev/null @@ -1,19 +0,0 @@ -declare module WebdriverIO { - interface ServiceOption extends StaticServerOptions {} -} - -interface StaticServerOptions { - /** - * Array of folder paths and mount points. - */ - folders?: Array; - /** - * Port to bind the server. - */ - port?: number; - /** - * Array of middleware objects. Load and instatiate these in the config, - * and pass them in for the static server to use. - */ - middleware?: Array; -} diff --git a/packages/wdio-static-server-service/tests/launcher.test.ts b/packages/wdio-static-server-service/tests/launcher.test.ts index 6d2322dfa58..6a10ed9390f 100644 --- a/packages/wdio-static-server-service/tests/launcher.test.ts +++ b/packages/wdio-static-server-service/tests/launcher.test.ts @@ -17,9 +17,9 @@ test('should be able to start server', async () => { folders: { mount: 'foo', path: 'bar' } }) await service.onPrepare({}); - (service.server.listen as jest.Mock).mock.calls[0][1] + (service['_server']!.listen as jest.Mock).mock.calls[0][1] expect(express).toBeCalledTimes(1) - expect(service.server.use).toBeCalledWith('foo', undefined) + expect(service['_server']!.use).toBeCalledWith('foo', undefined) expect(express.static).toBeCalledWith('bar') }) @@ -31,11 +31,11 @@ test('should be able to mount multiple folder', async () => { ] }) await service.onPrepare({}); - (service.server.listen as jest.Mock).mock.calls[0][1] + (service['_server']!.listen as jest.Mock).mock.calls[0][1] expect(express).toBeCalledTimes(1) - expect(service.server.use).toBeCalledWith('foo', undefined) + expect(service['_server']!.use).toBeCalledWith('foo', undefined) expect(express.static).toBeCalledWith('bar') - expect(service.server.use).toBeCalledWith('foo2', undefined) + expect(service['_server']!.use).toBeCalledWith('foo2', undefined) expect(express.static).toBeCalledWith('bar2') }) @@ -60,7 +60,7 @@ test('should register middlewares', async () => { middleware: [{ mount: 'foo', middleware: 'bar' }] }) await service.onPrepare({}) - expect(service.server.use).toBeCalledWith('foo', 'bar') + expect(service['_server']!.use).toBeCalledWith('foo', 'bar') }) afterEach(() => { diff --git a/packages/wdio-static-server-service/tsconfig.json b/packages/wdio-static-server-service/tsconfig.json index a32652092d3..d660189bdb9 100644 --- a/packages/wdio-static-server-service/tsconfig.json +++ b/packages/wdio-static-server-service/tsconfig.json @@ -6,6 +6,7 @@ "rootDir": "./src" }, "include": [ - "src/**/*" + "src/**/*", + "../../@types" ] } diff --git a/packages/wdio-static-server-service/tsconfig.prod.json b/packages/wdio-static-server-service/tsconfig.prod.json index c7e2194a8b4..2a0e7e84140 100644 --- a/packages/wdio-static-server-service/tsconfig.prod.json +++ b/packages/wdio-static-server-service/tsconfig.prod.json @@ -6,6 +6,7 @@ "rootDir": "./src" }, "include": [ - "src/**/*" + "src/**/*", + "../../@types" ] } diff --git a/packages/wdio-sumologic-reporter/package.json b/packages/wdio-sumologic-reporter/package.json index f95f7f0c609..46b95119fa2 100644 --- a/packages/wdio-sumologic-reporter/package.json +++ b/packages/wdio-sumologic-reporter/package.json @@ -26,6 +26,7 @@ "@types/json-stringify-safe": "^5.0.0", "@wdio/logger": "6.10.10", "@wdio/reporter": "6.11.0", + "@wdio/types": "^6.10.6", "dateformat": "^4.0.0", "got": "^11.0.2", "json-stringify-safe": "^5.0.1" @@ -35,5 +36,6 @@ }, "publishConfig": { "access": "public" - } + }, + "types": "./build/index.d.ts" } diff --git a/packages/wdio-sumologic-reporter/src/index.ts b/packages/wdio-sumologic-reporter/src/index.ts index 87f1a4dd778..fa8a1d37518 100644 --- a/packages/wdio-sumologic-reporter/src/index.ts +++ b/packages/wdio-sumologic-reporter/src/index.ts @@ -2,7 +2,7 @@ import got from 'got' import dateFormat from 'dateformat' import stringify from 'json-stringify-safe' -import WDIOReporter, { RunnerStats, SuiteStats, TestStats, WDIOReporterOptions } from '@wdio/reporter' +import WDIOReporter, { RunnerStats, SuiteStats, TestStats } from '@wdio/reporter' import logger from '@wdio/logger' import type { Options } from './types' @@ -23,7 +23,7 @@ export default class SumoLogicReporter extends WDIOReporter { private _isSynchronising = false private _hasRunnerEnd = false - constructor(options: WDIOReporterOptions) { + constructor(options: Options) { super(options) this._options = Object.assign({ // don't create a log file @@ -166,3 +166,11 @@ export default class SumoLogicReporter extends WDIOReporter { } } } + +export * from './types' + +declare global { + namespace WebdriverIO { + interface ReporterOption extends Options {} + } +} diff --git a/packages/wdio-sumologic-reporter/src/types.ts b/packages/wdio-sumologic-reporter/src/types.ts index 64c10337c26..5303a944466 100644 --- a/packages/wdio-sumologic-reporter/src/types.ts +++ b/packages/wdio-sumologic-reporter/src/types.ts @@ -1,6 +1,6 @@ -import { WDIOReporterOptionsFromStdout } from '@wdio/reporter' +import { Reporters } from '@wdio/types' -export interface Options extends WDIOReporterOptionsFromStdout { +export interface Options extends Partial { /** * define sync interval how often logs get pushed to Sumologic * @default 100 diff --git a/packages/wdio-sumologic-reporter/tsconfig.json b/packages/wdio-sumologic-reporter/tsconfig.json index 41f53514877..d660189bdb9 100644 --- a/packages/wdio-sumologic-reporter/tsconfig.json +++ b/packages/wdio-sumologic-reporter/tsconfig.json @@ -3,10 +3,10 @@ "compilerOptions": { "baseUrl": ".", "outDir": "./build", - "rootDir": "./src", - "types": ["webdriverio", "webdriver"] + "rootDir": "./src" }, "include": [ - "src/**/*" + "src/**/*", + "../../@types" ] } diff --git a/packages/wdio-sumologic-reporter/tsconfig.prod.json b/packages/wdio-sumologic-reporter/tsconfig.prod.json index 94d56e06ead..2a0e7e84140 100644 --- a/packages/wdio-sumologic-reporter/tsconfig.prod.json +++ b/packages/wdio-sumologic-reporter/tsconfig.prod.json @@ -3,10 +3,10 @@ "compilerOptions": { "baseUrl": ".", "outDir": "./build", - "rootDir": "./src", - "types": ["webdriverio", "webdriver"] + "rootDir": "./src" }, "include": [ - "src/**/*" + "src/**/*", + "../../@types" ] } diff --git a/packages/wdio-sync/package.json b/packages/wdio-sync/package.json index bd8e1fc6948..02694fbb9ff 100644 --- a/packages/wdio-sync/package.json +++ b/packages/wdio-sync/package.json @@ -9,7 +9,6 @@ "engines": { "node": ">=12.0.0" }, - "types": "./webdriverio.d.ts", "typeScriptVersion": "3.8.3", "repository": { "type": "git", @@ -29,9 +28,12 @@ "@types/fibers": "^3.1.0", "@types/puppeteer": "^5.4.0", "@wdio/logger": "6.10.10", - "fibers": "^5.0.0" + "@wdio/types": "^6.10.6", + "fibers": "^5.0.0", + "webdriverio": "^6.11.3" }, "publishConfig": { "access": "public" - } + }, + "types": "./build/index.d.ts" } diff --git a/packages/wdio-sync/src/index.ts b/packages/wdio-sync/src/index.ts index b46b0789da9..4981d1f3511 100644 --- a/packages/wdio-sync/src/index.ts +++ b/packages/wdio-sync/src/index.ts @@ -1,4 +1,5 @@ import Fiber from './fibers' +import type { Browser } from 'webdriverio' import executeHooksWithArgs from './executeHooksWithArgs' import runFnInFiberContext from './runFnInFiberContext' @@ -7,6 +8,11 @@ import wrapCommand from './wrapCommand' import { stackTraceFilter } from './utils' const defaultRetries = { attempts: 0, limit: 0 } +declare global { + var _HAS_FIBER_CONTEXT: boolean + var browser: any +} + /** * execute test or hook synchronously * @@ -15,7 +21,7 @@ const defaultRetries = { attempts: 0, limit: 0 } * @param {Array} args arguments passed to hook * @return {Promise} that gets resolved once test/hook is done or was retried enough */ -async function executeSync (this: WebdriverIO.BrowserObject, fn: Function, retries = defaultRetries, args: any[] = []): Promise { +async function executeSync (this: Browser<'async'>, fn: Function, retries = defaultRetries, args: any[] = []): Promise { /** * User can also use the `@wdio/sync` package directly to run commands * synchronously in standalone mode. In this case we neither have diff --git a/packages/wdio-sync/src/runFnInFiberContext.ts b/packages/wdio-sync/src/runFnInFiberContext.ts index 3dbcda1ac46..4cf3a94cb38 100644 --- a/packages/wdio-sync/src/runFnInFiberContext.ts +++ b/packages/wdio-sync/src/runFnInFiberContext.ts @@ -1,3 +1,5 @@ +import type { Browser } from 'webdriverio' + import Fiber from './fibers' /** @@ -6,7 +8,7 @@ import Fiber from './fibers' * @return {Function} wrapped around function */ export default function runFnInFiberContext (fn: Function) { - return function (this: WebdriverIO.BrowserObject, ...args: any[]) { + return function (this: Browser<'async'>, ...args: any[]) { delete global.browser._NOT_FIBER return new Promise((resolve, reject) => Fiber(() => { diff --git a/packages/wdio-sync/src/wrapCommand.ts b/packages/wdio-sync/src/wrapCommand.ts index 249a117d2f0..e73396be8dd 100644 --- a/packages/wdio-sync/src/wrapCommand.ts +++ b/packages/wdio-sync/src/wrapCommand.ts @@ -1,14 +1,16 @@ -import { Future } from './fibers' import logger from '@wdio/logger' +import type { Options } from '@wdio/types' +import type { Browser, Element, MultiRemoteBrowser } from 'webdriverio' import executeHooksWithArgs from './executeHooksWithArgs' import { sanitizeErrorMessage } from './utils' +import { Future } from './fibers' const log = logger('@wdio/sync') let inCommandHook = false const timers: any[] = [] -const elements: Set = new Set() +const elements: Set> = new Set() declare global { var WDIO_WORKER: boolean @@ -41,7 +43,7 @@ process.on('WDIO_TIMER', (payload) => { * @return {Function} actual wrapped function */ export default function wrapCommand (commandName: string, fn: Function) { - return function wrapCommandFn(this: WebdriverIO.BrowserObject | WebdriverIO.Element, ...args: any[]) { + return function wrapCommandFn(this: Browser<'async'> | Element<'async'>, ...args: any[]) { /** * print error if a user is using a fiberized command outside of the Fibers context */ @@ -56,14 +58,14 @@ export default function wrapCommand (commandName: string, fn: Function) { * store element if Timer is running to reset `_NOT_FIBER` if timeout has occurred */ if (timers.length > 0) { - elements.add(this as WebdriverIO.Element) + elements.add(this as Element<'async'>) } /** * Avoid running some functions in Future that are not in Fiber. */ if (this._NOT_FIBER === true) { - this._NOT_FIBER = isNotInFiber(this as WebdriverIO.Element, fn.name) + this._NOT_FIBER = isNotInFiber(this as Element<'async'>, fn.name) return fn.apply(this, args) } /** @@ -106,7 +108,7 @@ export default function wrapCommand (commandName: string, fn: Function) { * helper method that runs the command with before/afterCommand hook */ async function runCommandWithHooks( - this: WebdriverIO.BrowserObject | WebdriverIO.Element, + this: Browser<'async'> | Element<'async'>, commandName: string, fn: Function, ...args: any[] @@ -115,7 +117,7 @@ async function runCommandWithHooks( // should be before any async calls const stackError = new Error() - await runCommandHook.call(this, 'beforeCommand', this.options.beforeCommand, [commandName, args]) + await runCommandHook.call(this, 'beforeCommand', (this.options as Options.Testrunner).beforeCommand, [commandName, args]) let commandResult let commandError @@ -125,7 +127,7 @@ async function runCommandWithHooks( commandError = sanitizeErrorMessage(err, stackError) } - await runCommandHook.call(this, 'afterCommand', this.options.afterCommand, [commandName, args, commandResult, commandError]) + await runCommandHook.call(this, 'afterCommand', (this.options as Options.Testrunner).afterCommand, [commandName, args, commandResult, commandError]) if (commandError) { throw commandError @@ -148,31 +150,31 @@ async function runCommandHook(hookName: string, hookFn?: Function | Function[], * @param {object} context browser or element * @param {string} fnName function name */ -function isNotInFiber(context: WebdriverIO.Element, fnName: string) { - return fnName !== '' && !!(context.elementId || (context.parent && (context.parent as WebdriverIO.Element).elementId)) +function isNotInFiber(context: Element<'async'>, fnName: string) { + return fnName !== '' && !!(context.elementId || (context.parent && (context.parent as Element<'async'>).elementId)) } /** * set `_NOT_FIBER` to `false` for element and its parents * @param {object} context browser or element */ -function inFiber (context: WebdriverIO.BrowserObject | WebdriverIO.MultiRemoteBrowserObject) { - const multiRemoteContext = context as WebdriverIO.MultiRemoteBrowserObject +function inFiber (context: Browser<'async'> | Element<'async'> | MultiRemoteBrowser<'async'>) { + const multiRemoteContext = context as MultiRemoteBrowser<'async'> if (multiRemoteContext.constructor.name === 'MultiRemoteDriver') { return multiRemoteContext.instances.forEach(instance => { multiRemoteContext[instance]._NOT_FIBER = false - let parent = (multiRemoteContext[instance] as WebdriverIO.Element).parent + let parent = (multiRemoteContext[instance] as Element<'async'>).parent while (parent && parent._NOT_FIBER) { parent._NOT_FIBER = false - parent = (parent as WebdriverIO.Element).parent + parent = (parent as Element<'async'>).parent } }) } context._NOT_FIBER = false - let parent = (context as WebdriverIO.Element).parent + let parent = (context as Element<'async'>).parent while (parent && parent._NOT_FIBER) { parent._NOT_FIBER = false - parent = (parent as WebdriverIO.Element).parent + parent = (parent as Element<'async'>).parent } } diff --git a/packages/wdio-sync/tests/executeHooksWithArgs.test.ts b/packages/wdio-sync/tests/executeHooksWithArgs.test.ts index 4cd193add59..07118f32c11 100644 --- a/packages/wdio-sync/tests/executeHooksWithArgs.test.ts +++ b/packages/wdio-sync/tests/executeHooksWithArgs.test.ts @@ -1,12 +1,8 @@ import executeHooksWithArgs from '../src/executeHooksWithArgs' -declare global { - var browser: any -} - describe('executeHooksWithArgs', () => { beforeEach(() => { - global.browser = {} + global.browser = {} as any }) it('multiple hooks, multiple args', async () => { @@ -59,6 +55,7 @@ describe('executeHooksWithArgs', () => { }) afterEach(() => { + // @ts-expect-error delete global.browser }) }) diff --git a/packages/wdio-sync/tests/fibers.test.ts b/packages/wdio-sync/tests/fibers.test.ts index 6eb3a799f35..fbe45c312b5 100644 --- a/packages/wdio-sync/tests/fibers.test.ts +++ b/packages/wdio-sync/tests/fibers.test.ts @@ -1,7 +1,7 @@ export {} declare global { - var requireFibers: boolean + var requireFibers: boolean | undefined } global.requireFibers = true diff --git a/packages/wdio-sync/tests/index.test.ts b/packages/wdio-sync/tests/index.test.ts index 058936a8b9e..11a43a8b6a8 100644 --- a/packages/wdio-sync/tests/index.test.ts +++ b/packages/wdio-sync/tests/index.test.ts @@ -1,7 +1,9 @@ +import { Browser } from 'webdriverio' + import sync, { executeSync, runSync } from '../src' beforeEach(() => { - global.browser = {} + global.browser = {} as any as Browser }) it('exports a function to make async tests sync', async () => { @@ -12,27 +14,27 @@ it('exports a function to make async tests sync', async () => { describe('executeSync', () => { it('should pass with default values and regular fn', async () => { global.browser._NOT_FIBER = true - expect(await executeSync.call({}, () => 1, {})).toEqual(1) + expect(await executeSync.call(browser, () => 1, {} as any)).toEqual(1) expect(global.browser._NOT_FIBER).toBe(undefined) }) it('should pass with args and async fn', async () => { - const fn = async arg => arg + const fn = async (arg: any) => arg const scope = {} as any expect(await executeSync.call(scope, fn, { limit: 1, attempts: 0 }, [2])).toEqual(2) expect(scope.wdioRetries).toBe(0) }) it('should pass without scope', async () => { - const fn = async arg => arg - expect(await executeSync(fn, { limit: 1, attempts: 0 }, [2])).toEqual(2) + const fn = async (arg: any) => arg + expect(await executeSync.call(browser, fn, { limit: 1, attempts: 0 }, [2])).toEqual(2) }) it('should filter stack on failure', async () => { global.browser._NOT_FIBER = true let error try { - await executeSync.call({}, () => { throw new Error('foobar') }, {}) + await executeSync.call(browser, () => { throw new Error('foobar') }, {} as any) } catch (err) { error = err } @@ -43,11 +45,11 @@ describe('executeSync', () => { it('should not filter stack on failure if it is missing', async () => { let error try { - await executeSync.call({}, () => { + await executeSync.call(browser, () => { const err = new Error('foobar') err.stack = 'false' throw err - }, {}) + }, {} as any) } catch (err) { error = err } @@ -101,7 +103,7 @@ describe('runSync', () => { const rejectFn = jest.fn() const scope = {} - const fibersFn = runSync.call(scope, (arg) => 'foo' + arg, {}, ['bar']) + const fibersFn = runSync.call(scope, (arg: string) => 'foo' + arg, {} as any, ['bar']) await fibersFn(resolveFn, rejectFn) expect(rejectFn).not.toBeCalled() @@ -125,5 +127,6 @@ describe('runSync', () => { }) afterEach(() => { + // @ts-expect-error delete global.browser }) diff --git a/packages/wdio-sync/tests/runFnInFibersContext.test.ts b/packages/wdio-sync/tests/runFnInFibersContext.test.ts index 51fb2e0fb2d..d8c1a404ccb 100644 --- a/packages/wdio-sync/tests/runFnInFibersContext.test.ts +++ b/packages/wdio-sync/tests/runFnInFibersContext.test.ts @@ -1,28 +1,34 @@ +import { Browser } from 'webdriverio' + import runFnInFiberContext from '../src/runFnInFiberContext' beforeAll(() => { if (!global.browser) { - global.browser = {} + global.browser = {} as any as Browser } }) +interface Scope extends Browser{ + scopedVar: string +} + test('should wrap a successful running command', async () => { global.browser._NOT_FIBER = true - const wrappedFn = runFnInFiberContext(function (arg) { + const wrappedFn = runFnInFiberContext(function (this: Scope, arg: string) { return this.scopedVar + arg }) - expect(await wrappedFn.call({ scopedVar: 'foo' }, 'bar')).toBe('foobar') + expect(await wrappedFn.call({ scopedVar: 'foo' } as Scope, 'bar')).toBe('foobar') expect(global.browser._NOT_FIBER).toBe(undefined) }) test('should wrap a failing running command', async () => { global.browser._NOT_FIBER = true - const wrappedFn = runFnInFiberContext(function (arg) { + const wrappedFn = runFnInFiberContext(function (this: Scope, arg: string) { throw new Error(this.scopedVar + arg) }) - await expect(wrappedFn.call({ scopedVar: 'foo' }, 'bar')) + await expect(wrappedFn.call({ scopedVar: 'foo' } as Scope, 'bar')) .rejects.toThrow(new Error('foobar')) expect(global.browser._NOT_FIBER).toBe(undefined) }) diff --git a/packages/wdio-sync/tests/wrapCommand.test.ts b/packages/wdio-sync/tests/wrapCommand.test.ts index 9186d502e64..c52b1e797f2 100644 --- a/packages/wdio-sync/tests/wrapCommand.test.ts +++ b/packages/wdio-sync/tests/wrapCommand.test.ts @@ -15,6 +15,7 @@ const futurePrototypeWait = Future.prototype.wait describe('wrapCommand:runCommand', () => { beforeEach(() => { jest.resetAllMocks() + // @ts-expect-error delete global.WDIO_WORKER }) @@ -23,7 +24,7 @@ describe('wrapCommand:runCommand', () => { global.WDIO_WORKER = true const fn = jest.fn(x => (x + x)) const runCommand = wrapCommand('foo', fn) - const result = await runCommand.call({ options: {} }, 'bar') + const result = await runCommand.call({ options: {} } as any, 'bar') expect(result).toEqual('barbar') process.emit('WDIO_TIMER' as any, { id: 0 } as any) }) @@ -31,7 +32,7 @@ describe('wrapCommand:runCommand', () => { it('should set _NOT_FIBER to false if elementId is missing', async () => { const fn = jest.fn() const runCommand = wrapCommand('foo', fn) - const context = { options: {}, _NOT_FIBER: true } + const context = { options: {}, _NOT_FIBER: true } as any await runCommand.call(context, 'bar') expect(context._NOT_FIBER).toBe(false) }) @@ -39,14 +40,14 @@ describe('wrapCommand:runCommand', () => { it('should set _NOT_FIBER to true if elementId is exist', async () => { const fn = jest.fn() const runCommand = wrapCommand('foo', fn) - const context = { options: {}, _NOT_FIBER: true, elementId: 'foo' } + const context = { options: {}, _NOT_FIBER: true, elementId: 'foo' } as any await runCommand.call(context, 'bar') expect(context._NOT_FIBER).toBe(true) }) it('should set _NOT_FIBER to false if elementId is exist but function is anonymous', async () => { const runCommand = wrapCommand('foo', () => { }) - const context = { options: {}, _NOT_FIBER: true, elementId: 'foo' } + const context = { options: {}, _NOT_FIBER: true, elementId: 'foo' } as any await runCommand.call(context, 'bar') expect(context._NOT_FIBER).toBe(false) }) @@ -54,7 +55,7 @@ describe('wrapCommand:runCommand', () => { it('should set _NOT_FIBER to true if parent.elementId is exist', async () => { const fn = jest.fn() const runCommand = wrapCommand('foo', fn) - const context = { options: {}, _NOT_FIBER: true, parent: { elementId: 'foo' } } + const context = { options: {}, _NOT_FIBER: true, parent: { elementId: 'foo' } } as any await runCommand.call(context, 'bar') expect(context._NOT_FIBER).toBe(true) }) @@ -66,7 +67,7 @@ describe('wrapCommand:runCommand', () => { const context = { _NOT_FIBER: undefined, options: {}, elementId: 'foo', parent: { _NOT_FIBER: true } - } + } as any await runCommand.call(context) expect(context._NOT_FIBER).toEqual(false) @@ -84,7 +85,7 @@ describe('wrapCommand:runCommand', () => { this._hidden_changes_.push(val) this._hidden_ = val } - } + } as any await runCommand.call(context) expect(context._hidden_changes_).toEqual([false, false]) @@ -123,7 +124,7 @@ describe('wrapCommand:runCommand', () => { context._hidden_ = val } } - } + } as any await runCommand.call(context) expect(context._hidden_changes_).toEqual(['browserA false', 'parent false', 'browserB false']) @@ -132,14 +133,14 @@ describe('wrapCommand:runCommand', () => { it('should throw error with proper message', async () => { const fn = jest.fn(x => { throw new Error(x) }) const runCommand = wrapCommand('foo', fn) - const result = runCommand.call({ options: {} }, 'bar') + const result = runCommand.call({ options: {} } as any, 'bar') await expect(result).rejects.toEqual(new Error('bar')) }) it('should contain merged error stack', async () => { const fn = jest.fn(() => { throw anotherError }) const runCommand = wrapCommand('foo', fn) - const result = runCommand.call({ options: {} }, 'bar') + const result = runCommand.call({ options: {} } as any, 'bar') try { await result } catch (err) { @@ -154,7 +155,7 @@ describe('wrapCommand:runCommand', () => { it('should accept non Error objects', async () => { const fn = jest.fn(x => Promise.reject(x)) const runCommand = wrapCommand('foo', fn) - const result = runCommand.call({ options: {} }, 'bar') + const result = runCommand.call({ options: {} } as any, 'bar') try { await result } catch (err) { @@ -170,7 +171,7 @@ describe('wrapCommand:runCommand', () => { const fn = jest.fn(() => Promise.reject()) const runCommand = wrapCommand('foo', fn) - const result = runCommand.call({ options: {} }) + const result = runCommand.call({ options: {} } as any) try { await result } catch (err) { @@ -188,7 +189,7 @@ describe('wrapCommand:runCommand', () => { it('should throw regular error', () => { const fn = jest.fn(() => {}) const runCommand = wrapCommand('foo', fn) - const context = { options: {}, _NOT_FIBER: undefined } + const context = { options: {}, _NOT_FIBER: undefined } as any try { runCommand.call(context, 'bar') } catch (err) { diff --git a/packages/wdio-sync/tsconfig.json b/packages/wdio-sync/tsconfig.json index e0952f8f6bb..d660189bdb9 100644 --- a/packages/wdio-sync/tsconfig.json +++ b/packages/wdio-sync/tsconfig.json @@ -3,10 +3,10 @@ "compilerOptions": { "baseUrl": ".", "outDir": "./build", - "rootDir": "./src", - "types": ["webdriverio"] + "rootDir": "./src" }, "include": [ - "src/**/*" + "src/**/*", + "../../@types" ] } diff --git a/packages/wdio-sync/tsconfig.prod.json b/packages/wdio-sync/tsconfig.prod.json index 8ff66adac36..2a0e7e84140 100644 --- a/packages/wdio-sync/tsconfig.prod.json +++ b/packages/wdio-sync/tsconfig.prod.json @@ -3,10 +3,10 @@ "compilerOptions": { "baseUrl": ".", "outDir": "./build", - "rootDir": "./src", - "types": ["webdriverio"] + "rootDir": "./src" }, "include": [ - "src/**/*" + "src/**/*", + "../../@types" ] } diff --git a/packages/wdio-sync/webdriverio-core.d.ts b/packages/wdio-sync/webdriverio-core.d.ts deleted file mode 100644 index 0f3acf6fadb..00000000000 --- a/packages/wdio-sync/webdriverio-core.d.ts +++ /dev/null @@ -1,1436 +0,0 @@ -// -------------------- ATTENTION -------------------- -// Do not edit this file as it gets auto-generated! -// For edits modify /scripts/templates/*.tpl.d.ts -// Check CONTRIBUTING.md for more details. -// -------------------------------------------------- -// -/// -/// - -// See https://github.com/DefinitelyTyped/DefinitelyTyped/issues/24419 -interface Element { } -interface Node { } -interface NodeListOf { } - -declare namespace WebdriverIO { - type LocationParam = 'x' | 'y'; - - interface LocationReturn { - x: number, - y: number - } - - type SizeParam = 'width' | 'height'; - - interface SizeReturn { - width: number, - height: number - } - - interface CSSProperty { - property: string; - value: any; - parsed?: { - // other - unit?: string; - // font-family - value?: any; - string: string; - // color - hex?: string; - alpha?: number; - type?: string; - rgba?: string - } - } - - type JsonPrimitive = string | number | boolean | null; - type JsonObject = { [x: string]: JsonPrimitive | JsonObject | JsonArray }; - type JsonArray = Array; - type JsonCompatible = JsonObject | JsonArray; - - interface MultiRemoteBrowserOptions { - sessionId?: string - capabilities: WebDriver.DesiredCapabilities; - } - - interface MultiRemoteCapabilities { - [instanceName: string]: MultiRemoteBrowserOptions; - } - - - interface ServiceOption { - [key: string]: any; - } - - interface RunnerInstance { - initialise(): Promise - shutdown(): Promise - getWorkerCount(): number - run(args: any): NodeJS.EventEmitter - workerPool: any - } - - interface ServiceClass { - new(options: ServiceOption, caps: WebDriver.DesiredCapabilities, config: Options): ServiceInstance - } - - interface RunnerClass { - new(configFile: string, config: Omit): RunnerInstance - } - - interface ServicePlugin extends ServiceClass { - default: ServiceClass - launcher?: ServiceClass - } - - interface RunnerPlugin extends RunnerClass { - default: RunnerClass - launcher?: RunnerClass - } - - interface ServiceInstance extends HookFunctions { - options?: Record, - capabilities?: WebDriver.DesiredCapabilities, - config?: Config - } - - type ServiceEntry = ( - /** - * e.g. `services: ['@wdio/sauce-service']` - */ - string | - /** - * e.g. `services: [{ onPrepare: () => { ... } }]` - */ - HookFunctions | - /** - * e.g. `services: [CustomClass]` - */ - ServiceClass | - /** - * e.g. `services: [['@wdio/sauce-service', { ... }]]` - */ - [string, ServiceOption] | - /** - * e.g. `services: [[CustomClass, { ... }]]` - */ - [ServiceClass, ServiceOption] - ) - - interface Options { - /** - * Define the protocol you want to use for your browser automation. - * Currently only [`webdriver`](https://www.npmjs.com/package/webdriver) and - * [`devtools`](https://www.npmjs.com/package/devtools) are supported, - * as these are the main browser automation technologies available. - */ - automationProtocol?: string; - runner?: string; - /** - * Your cloud service username (only works for Sauce Labs, Browserstack, TestingBot, - * CrossBrowserTesting or LambdaTest accounts). If set, WebdriverIO will automatically - * set connection options for you. - */ - user?: string; - /** - * Your cloud service access key or secret key (only works for Sauce Labs, Browserstack, - * TestingBot, CrossBrowserTesting or LambdaTest accounts). If set, WebdriverIO will - * automatically set connection options for you. - */ - key?: string; - /** - * If running on Sauce Labs, you can choose to run tests between different datacenters: - * US or EU. To change your region to EU, add region: 'eu' to your config. - */ - region?: string; - /** - * Sauce Labs provides a headless offering that allows you to run Chrome and Firefox tests headless. - */ - headless?: boolean; - /** - * Define specs for test execution. - */ - specs?: string[]; - /** - * Exclude specs from test execution. - */ - exclude?: string[]; - /** - * Files to watch when running `wdio` with the `--watch` flag. - */ - filesToWatch?: string[], - /** - * An object describing various of suites, which you can then specify - * with the --suite option on the wdio CLI. - */ - suites?: Record; - /** - * Maximum number of total parallel running workers. - */ - maxInstances?: number; - /** - * Maximum number of total parallel running workers per capability. - */ - maxInstancesPerCapability?: number; - capabilities?: WebDriver.DesiredCapabilities[] | MultiRemoteCapabilities; - /** - * Directory to store all testrunner log files (including reporter logs and wdio logs). - * If not set, all logs are streamed to stdout. Since most reporters are made to log to - * stdout, it is recommended to only use this option for specific reporters where it - * makes more sense to push report into a file (like the junit reporter, for example). - * - * When running in standalone mode, the only log generated by WebdriverIO will be the wdio log. - */ - outputDir?: string; - /** - * Shorten url command calls by setting a base URL. - */ - baseUrl?: string; - /** - * If you want your test run to stop after a specific number of test failures, use bail. - * (It defaults to 0, which runs all tests no matter what.) Note: Please be aware that - * when using a third party test runner (such as Mocha), additional configuration might - * be required. - */ - bail?: number; - /** - * The number of retry attempts for an entire specfile when it fails as a whole. - */ - specFileRetries?: number; - /** - * Delay in seconds between the spec file retry attempts - */ - specFileRetriesDelay?: number; - /** - * Whether or not retried specfiles should be retried immediately or deferred to the end of the queue - */ - specFileRetriesDeferred?: boolean - /** - * Default timeout for all `waitFor*` commands. (Note the lowercase f in the option name.) - * This timeout only affects commands starting with `waitFor*` and their default wait time. - */ - waitforTimeout?: number; - /** - * Default interval for all `waitFor*` commands to check if an expected state (e.g., - * visibility) has been changed. - */ - waitforInterval?: number; - /** - * Defines the test framework to be used by the WDIO testrunner. - */ - framework?: string; - /** - * List of reporters to use. A reporter can be either a string, or an array of - * `['reporterName', { }]` where the first element is a string - * with the reporter name and the second element an object with reporter options. - */ - reporters?: (string | object)[]; - /** - * Determines in which interval the reporter should check if they are synchronised - * if they report their logs asynchronously (e.g. if logs are streamed to a 3rd - * party vendor). - */ - reporterSyncInterval?: number; - /** - * Determines the maximum time reporters have to finish uploading all their logs - * until an error is being thrown by the testrunner. - */ - reporterSyncTimeout?: number; - /** - * Services take over a specific job you don't want to take care of. They enhance - * your test setup with almost no effort. - */ - services?: ServiceEntry[]; - /** - * Node arguments to specify when launching child processes. - */ - execArgv?: string[]; - } - - interface RemoteOptions extends WebDriver.Options, HookFunctions, Omit { } - - interface MultiRemoteOptions { - [instanceName: string]: WebDriver.DesiredCapabilities; - } - - interface Suite { - error?: any; - } - interface Test {} - interface TestResult { - error?: any, - result?: any, - passed: boolean, - duration: number, - retries: { limit: number, attempts: number } - } - - interface Results { - finished: number, - passed: number, - failed: number - } - - interface HookFunctions { - /** - * Gets executed once before all workers get launched. - * @param config wdio configuration object - * @param capabilities list of capabilities details - */ - onPrepare?( - config: Config, - capabilities: WebDriver.DesiredCapabilities[] - ): void; - - /** - * Gets executed before a worker process is spawned and can be used to initialise specific service - * for that worker as well as modify runtime environments in an async fashion. - * @param cid capability id (e.g 0-0) - * @param caps object containing capabilities for session that will be spawn in the worker - * @param specs specs to be run in the worker process - * @param args object that will be merged with the main configuration once worker is initialised - * @param execArgv list of string arguments passed to the worker process - */ - onWorkerStart?( - cid: string, - caps: WebDriver.DesiredCapabilities, - specs: string[], - args: Config, - execArgv: string[] - ): void; - - /** - * Gets executed after all workers got shut down and the process is about to exit. An error - * thrown in the onComplete hook will result in the test run failing. - * @param exitCode runner exit code - * @param config wdio configuration object - * @param capabilities list of capabilities details - * @param results test results - */ - onComplete?( - exitCode: number, - config: Config, - capabilities: WebDriver.DesiredCapabilities, - results: Results - ): void; - - /** - * Gets executed when a refresh happens. - * @param oldSessionId session id of old session - * @param newSessionId session id of new session - */ - onReload?( - oldSessionId: string, - newSessionId: string - ): void; - - /** - * Gets executed before test execution begins. At this point you can access to all global - * variables like `browser`. It is the perfect place to define custom commands. - * @param capabilities list of capabilities details - * @param specs specs to be run in the worker process - * @param browser instance of created browser/device session - */ - before?( - capabilities: WebDriver.DesiredCapabilities, - specs: string[], - browser: BrowserObject - ): void; - - /** - * Runs before a WebdriverIO command gets executed. - * @param commandName command name - * @param args arguments that command would receive - */ - beforeCommand?( - commandName: string, - args: any[] - ): void; - - /** - * Hook that gets executed _before_ a hook within the suite starts (e.g. runs before calling - * beforeEach in Mocha). `stepData` and `world` are Cucumber framework specific properties. - * @param test details to current running test (or step in Cucumber) - * @param context context to current running test - * @param stepData Cucumber step data - * @param world Cucumber world - */ - beforeHook?(test: any, context: any, stepData?: any, world?: any): void; - - /** - * Gets executed just before initialising the webdriver session and test framework. It allows you - * to manipulate configurations depending on the capability or spec. - * @param config wdio configuration object - * @param capabilities list of capabilities details - * @param specs list of spec file paths that are to be run - */ - beforeSession?( - config: Config, - capabilities: WebDriver.DesiredCapabilities, - specs: string[] - ): void; - - /** - * Hook that gets executed before the suite starts. - * @param suite suite details - */ - beforeSuite?(suite: Suite): void; - - /** - * Function to be executed before a test (in Mocha/Jasmine) starts. - * @param test details to current running test (or step in Cucumber) - * @param context context to current running test - */ - beforeTest?(test: Test, context: any): void; - - /** - * Hook that gets executed _after_ a hook within the suite ends (e.g. runs after calling - * afterEach in Mocha). `stepData` and `world` are Cucumber framework specific. - * @param test details to current running test (or step in Cucumber) - * @param context context to current running test - * @param result test result - * @param stepData Cucumber step data - * @param world Cucumber world - */ - afterHook?(test: any, context: any, result: TestResult, stepData?: any, world?: any): void; - - /** - * Gets executed after all tests are done. You still have access to all global variables from - * the test. - * @param result number of total failing tests - * @param capabilities list of capabilities details - * @param specs list of spec file paths that are to be run - */ - after?( - result: number, - capabilities: WebDriver.DesiredCapabilities, - specs: string[] - ): void; - - /** - * Runs after a WebdriverIO command gets executed - * @param commandName command name - * @param args arguments that command would receive - * @param result result of the command - * @param error error in case something went wrong - */ - afterCommand?( - commandName: string, - args: any[], - result: any, - error?: Error - ): void; - - /** - * Gets executed right after terminating the webdriver session. - * @param config wdio configuration object - * @param capabilities list of capabilities details - * @param specs list of spec file paths that are to be run - */ - afterSession?( - config: Config, - capabilities: WebDriver.DesiredCapabilities, - specs: string[] - ): void; - - /** - * Hook that gets executed after the suite has ended - * @param suite suite details - */ - afterSuite?(suite: Suite): void; - - /** - * Function to be executed after a test (in Mocha/Jasmine) ends. - * @param test details to current running test (or step in Cucumber) - * @param context context to current running test - * @param result test result - */ - afterTest?(test: Test, context: any, result: TestResult): void; - } - type _HooksArray = { - [K in keyof Pick]: HookFunctions[K] | Array; - }; - type _Hooks = Omit; - interface Hooks extends _HooksArray, _Hooks { } - - type ActionTypes = 'press' | 'longPress' | 'tap' | 'moveTo' | 'wait' | 'release'; - interface TouchAction { - action: ActionTypes, - x?: number, - y?: number, - element?: Element, - ms?: number - } - type TouchActionParameter = string | string[] | TouchAction | TouchAction[]; - type TouchActions = TouchActionParameter | TouchActionParameter[]; - - type WaitForOptions = { - timeout?: number, - interval?: number, - timeoutMsg?: string, - reverse?: boolean, - } - - type Matcher = { - name: string, - args: Array - class?: string - } - - type ReactSelectorOptions = { - props?: object, - state?: any[] | number | string | object | boolean - } - - type MoveToOptions = { - xOffset?: number, - yOffset?: number - } - - type DragAndDropOptions = { - duration?: number - } - - type NewWindowOptions = { - windowName?: string, - windowFeatures?: string - } - - type PDFPrintOptions = { - orientation?: string, - scale?: number, - background?: boolean, - width?: number, - height?: number, - top?: number, - bottom?: number, - left?: number, - right?: number, - shrinkToFit?: boolean, - pageRanges?: object[] - } - - type ClickOptions = { - button?: number | string, - x?: number, - y?: number - } - - type WaitUntilOptions = { - timeout?: number, - timeoutMsg?: string, - interval?: number - } - - type DragAndDropCoordinate = { - x: number, - y: number - } - - /** - * HTTP request data. (copied from the puppeteer-core package as there is currently - * no way to access these types otherwise) - */ - type ResourcePriority = 'VeryLow' | 'Low' | 'Medium' | 'High' | 'VeryHigh'; - type MixedContentType = 'blockable' | 'optionally-blockable' | 'none'; - type ReferrerPolicy = 'unsafe-url' | 'no-referrer-when-downgrade' | 'no-referrer' | 'origin' | 'origin-when-cross-origin' | 'same-origin' | 'strict-origin' | 'strict-origin-when-cross-origin'; - interface Request { - /** - * Request URL (without fragment). - */ - url: string; - /** - * Fragment of the requested URL starting with hash, if present. - */ - urlFragment?: string; - /** - * HTTP request method. - */ - method: string; - /** - * HTTP request headers. - */ - headers: Record; - /** - * HTTP POST request data. - */ - postData?: string; - /** - * True when the request has POST data. Note that postData might still be omitted when this flag is true when the data is too long. - */ - hasPostData?: boolean; - /** - * The mixed content type of the request. - */ - mixedContentType?: MixedContentType; - /** - * Priority of the resource request at the time request is sent. - */ - initialPriority: ResourcePriority; - /** - * The referrer policy of the request, as defined in https://www.w3.org/TR/referrer-policy/ - */ - referrerPolicy: ReferrerPolicy; - /** - * Whether is loaded via link preload. - */ - isLinkPreload?: boolean; - } - - interface Matches extends Request { - /** - * body response of actual resource - */ - body: string | Buffer | JsonCompatible - /** - * HTTP response headers. - */ - responseHeaders: Record; - /** - * HTTP response status code. - */ - statusCode: number; - } - - type PuppeteerBrowser = import('puppeteer').Browser; - type CDPSession = Partial; - type MockOverwriteFunction = (request: Matches, client: CDPSession) => Promise>; - type MockOverwrite = string | Record | MockOverwriteFunction; - - type MockResponseParams = { - statusCode?: number | ((request: Matches) => number), - headers?: Record | ((request: Matches) => Record), - /** - * fetch real response before responding with mocked data. Default: true - */ - fetchResponse?: boolean - } - - type MockFilterOptions = { - method?: string | ((method: string) => boolean), - headers?: Record | ((headers: Record) => boolean), - requestHeaders?: Record | ((headers: Record) => boolean), - responseHeaders?: Record | ((headers: Record) => boolean), - statusCode?: number | ((statusCode: number) => boolean), - postData?: string | ((payload: string | undefined) => boolean) - } - - type ErrorCode = 'Failed' | 'Aborted' | 'TimedOut' | 'AccessDenied' | 'ConnectionClosed' | 'ConnectionReset' | 'ConnectionRefused' | 'ConnectionAborted' | 'ConnectionFailed' | 'NameNotResolved' | 'InternetDisconnected' | 'AddressUnreachable' | 'BlockedByClient' | 'BlockedByResponse' - - type ThrottlePreset = 'offline' | 'GPRS' | 'Regular2G' | 'Good2G' | 'Regular3G' | 'Good3G' | 'Regular4G' | 'DSL' | 'WiFi' | 'online' - interface CustomThrottle { - offline: boolean, - downloadThroughput: number, - uploadThroughput: number, - latency: number - } - type ThrottleOptions = ThrottlePreset | CustomThrottle - - type AddCommandFn = (this: IsElement extends true ? Element : BrowserObject, ...args: any[]) => any - type OverwriteCommandFn = (this: IsElement extends true ? Element : BrowserObject, origCommand: IsElement extends true ? Element[ElementKey] : BrowserObject[BrowserKey], ...args: any[]) => any - - interface Element extends BrowserObject { - selector: string; - elementId: string; - - /** - * w3c - */ - "element-6066-11e4-a52e-4f735466cecf"?: string; - - /** - * jsonwp - */ - ELEMENT?: string; - - /** - * index in array of elements - * only applicable if the element found with `$$` command - */ - index?: number; - - /** - * WebdriverIO.Element or WebdriverIO.BrowserObject - */ - parent: Element | WebdriverIO.BrowserObject; - - /** - * true if element is a React component - */ - isReactElement?: boolean - - /** - * add command to `element` scope - */ - addCommand( - name: string, - func: AddCommandFn - ): void; - - /** - * The `$$` command is a short way to call the [`findElements`](/docs/api/webdriver.html#findelements) command in order - * to fetch multiple elements on the page similar to the `$$` command from the browser scope. The difference when calling - * it from an element scope is that the driver will look within the children of that element. - */ - $$( - selector: string | Function | Matcher - ): ElementArray; - - /** - * The `$` command is a short way to call the [`findElement`](/docs/api/webdriver.html#findelement) command in order - * to fetch a single element on the page similar to the `$` command from the browser scope. The difference when calling - * it from an element scope is that the driver will look within the children of that element. You can also pass in an object as selector - * where the object contains a property `element-6066-11e4-a52e-4f735466cecf` with the value of a reference - * to an element. The command will then transform the reference to an extended WebdriverIO element. - */ - $( - selector: string | Function | Matcher - ): Element; - - /** - * Add a value to an object found by given selector. You can also use unicode - * characters like Left arrow or Back space. WebdriverIO will take care of - * translating them into unicode characters. You’ll find all supported characters - * [here](https://w3c.github.io/webdriver/webdriver-spec.html#keyboard-actions). - * To do that, the value has to correspond to a key from the table. It can be disabled - * by setting `translateToUnicode` optional parameter to false. - */ - addValue( - value: string | number | boolean | object | any[], - options?: AddValueOptions - ): void; - - /** - * Clear a `