From 28e64e439ffc36a95f24aeda9f1d21111429dfa3 Mon Sep 17 00:00:00 2001 From: Christian Bromann Date: Sun, 3 Mar 2024 15:42:28 -0800 Subject: [PATCH] (devtools): removing `devtools` automation protocol (#12301) * (devtools): bye bye * remove e2e tests * more cleanuops * fix unit tests * fix test command --- .gitignore | 1 - CONTRIBUTING.md | 5 +- PROJECT_CHARTER.md | 4 +- README.md | 3 +- __mocks__/@wdio/utils.ts | 4 +- __mocks__/puppeteer-core.ts | 1 + e2e/attach.test.ts | 7 +- e2e/element.test.ts | 316 ------- e2e/interop/devtools.e2e.js | 18 - e2e/protocol.test.ts | 399 --------- e2e/screenshot.png | Bin 0 -> 34998 bytes e2e/standalone/edge.e2e.ts | 37 - e2e/standalone/firefox.e2e.ts | 40 - e2e/standalone/vitest.config.ts | 11 - e2e/transitions.test.ts | 64 -- e2e/wdio/headless/secondTest.e2e.ts | 2 +- package.json | 4 +- packages/devtools/.npmignore | 4 - packages/devtools/LICENSE-MIT | 20 - packages/devtools/README.md | 157 ---- packages/devtools/package.json | 62 -- packages/devtools/src/cjs/index.ts | 42 - packages/devtools/src/cjs/package.json | 3 - packages/devtools/src/commands.ts | 69 -- packages/devtools/src/commands/acceptAlert.ts | 17 - packages/devtools/src/commands/addCookie.ts | 32 - packages/devtools/src/commands/back.ts | 17 - packages/devtools/src/commands/closeWindow.ts | 33 - .../devtools/src/commands/createWindow.ts | 43 - .../devtools/src/commands/deleteAllCookies.ts | 19 - .../devtools/src/commands/deleteCookie.ts | 24 - .../devtools/src/commands/deleteSession.ts | 14 - .../devtools/src/commands/dismissAlert.ts | 19 - .../devtools/src/commands/elementClear.ts | 25 - .../devtools/src/commands/elementClick.ts | 56 -- .../devtools/src/commands/elementSendKeys.ts | 64 -- .../src/commands/executeAsyncScript.ts | 45 - .../devtools/src/commands/executeScript.ts | 59 -- packages/devtools/src/commands/findElement.ts | 40 - .../src/commands/findElementFromElement.ts | 45 - .../src/commands/findElementFromShadowRoot.ts | 29 - .../devtools/src/commands/findElements.ts | 40 - .../src/commands/findElementsFromElement.ts | 45 - .../commands/findElementsFromShadowRoot.ts | 29 - packages/devtools/src/commands/forward.ts | 15 - .../devtools/src/commands/getActiveElement.ts | 43 - .../devtools/src/commands/getAlertText.ts | 17 - .../devtools/src/commands/getAllCookies.ts | 14 - .../src/commands/getElementAttribute.ts | 25 - .../src/commands/getElementCSSValue.ts | 26 - .../src/commands/getElementComputedLabel.ts | 26 - .../src/commands/getElementComputedRole.ts | 26 - .../src/commands/getElementProperty.ts | 29 - .../devtools/src/commands/getElementRect.ts | 24 - .../src/commands/getElementShadowRoot.ts | 29 - .../src/commands/getElementTagName.ts | 25 - .../devtools/src/commands/getElementText.ts | 26 - .../devtools/src/commands/getNamedCookie.ts | 26 - .../devtools/src/commands/getPageSource.ts | 14 - packages/devtools/src/commands/getTimeouts.ts | 16 - packages/devtools/src/commands/getTitle.ts | 14 - packages/devtools/src/commands/getUrl.ts | 14 - .../devtools/src/commands/getWindowHandle.ts | 13 - .../devtools/src/commands/getWindowHandles.ts | 14 - .../devtools/src/commands/getWindowRect.ts | 19 - packages/devtools/src/commands/index.ts | 69 -- .../devtools/src/commands/isElementEnabled.ts | 19 - .../src/commands/isElementSelected.ts | 23 - packages/devtools/src/commands/navigateTo.ts | 24 - packages/devtools/src/commands/newSession.ts | 36 - .../devtools/src/commands/performActions.ts | 221 ----- packages/devtools/src/commands/refresh.ts | 15 - .../devtools/src/commands/releaseActions.ts | 11 - .../devtools/src/commands/sendAlertText.ts | 21 - packages/devtools/src/commands/setTimeouts.ts | 19 - .../devtools/src/commands/setWindowRect.ts | 22 - packages/devtools/src/commands/status.ts | 34 - .../devtools/src/commands/switchToFrame.ts | 85 -- .../src/commands/switchToParentFrame.ts | 28 - .../devtools/src/commands/switchToWindow.ts | 26 - .../src/commands/takeElementScreenshot.ts | 28 - .../devtools/src/commands/takeScreenshot.ts | 18 - packages/devtools/src/constants.ts | 186 ---- packages/devtools/src/devtoolsdriver.ts | 328 ------- packages/devtools/src/elementstore.ts | 52 -- packages/devtools/src/finder/edge.ts | 141 --- packages/devtools/src/finder/finder.ts | 35 - packages/devtools/src/finder/firefox.ts | 131 --- packages/devtools/src/finder/index.ts | 16 - packages/devtools/src/index.ts | 186 ---- packages/devtools/src/launcher.ts | 269 ------ packages/devtools/src/scripts/.eslintrc.mjs | 5 - .../scripts/cleanUpSerializationSelector.ts | 10 - packages/devtools/src/scripts/createWindow.ts | 3 - packages/devtools/src/scripts/elementClear.ts | 6 - .../src/scripts/executeAsyncScript.ts | 52 -- .../devtools/src/scripts/executeScript.ts | 36 - .../devtools/src/scripts/getActiveElement.ts | 11 - .../src/scripts/getElementAttribute.ts | 3 - .../src/scripts/getElementCSSValue.ts | 6 - .../devtools/src/scripts/getElementRect.ts | 7 - .../devtools/src/scripts/getElementTagName.ts | 3 - .../devtools/src/scripts/getElementText.ts | 3 - packages/devtools/src/scripts/getUrl.ts | 3 - packages/devtools/src/scripts/selectOption.ts | 11 - packages/devtools/src/types.ts | 61 -- packages/devtools/src/utils.ts | 414 --------- .../tests/__snapshots__/devtools.test.ts.snap | 33 - .../__snapshots__/devtoolsdriver.test.ts.snap | 164 ---- .../tests/__snapshots__/launcher.test.ts.snap | 508 ----------- .../tests/__snapshots__/utils.test.ts.snap | 241 ----- packages/devtools/tests/devtools.test.ts | 84 -- .../devtools/tests/devtoolsdriver.test.ts | 378 -------- packages/devtools/tests/elementStore.test.ts | 57 -- packages/devtools/tests/finder/finder.test.ts | 41 - .../devtools/tests/finder/systemProfiler.json | 60 -- packages/devtools/tests/index.test.ts | 94 -- packages/devtools/tests/launcher.test.ts | 756 ---------------- packages/devtools/tests/utils.test.ts | 426 --------- packages/devtools/tsconfig.json | 14 - packages/devtools/tsconfig.prod.json | 14 - .../wdio-allure-reporter/tests/suite.test.ts | 822 +++++++++--------- .../src/vite/plugins/testrunner.ts | 2 +- packages/wdio-cli/src/utils.ts | 2 +- .../wdio-devtools-service/tests/utils.test.ts | 8 +- packages/wdio-types/src/Automation.ts | 20 + packages/wdio-types/src/Capabilities.ts | 2 +- packages/wdio-types/src/Options.ts | 4 +- packages/wdio-types/src/index.ts | 4 +- packages/wdio-utils/src/envDetector.ts | 53 +- packages/wdio-utils/src/index.ts | 6 +- packages/wdio-utils/src/node/manager.ts | 16 +- packages/wdio-utils/tests/envDetector.test.ts | 11 +- packages/wdio-utils/tests/node/index.test.ts | 1 - .../wdio-utils/tests/node/manager.test.ts | 57 -- packages/webdriver/src/types.ts | 1 - packages/webdriver/tests/command.test.ts | 1 - packages/webdriverio/README.md | 2 +- packages/webdriverio/package.json | 5 - .../webdriverio/src/commands/browser/keys.ts | 4 +- packages/webdriverio/src/constants.ts | 20 - packages/webdriverio/src/protocol-stub.ts | 7 +- packages/webdriverio/src/utils/driver.ts | 58 +- packages/webdriverio/src/utils/index.ts | 10 +- packages/webdriverio/tests/addCommand.test.ts | 1 - .../tests/commands/browser/mock.test.ts | 1 - .../commands/browser/reloadSession.test.ts | 1 - .../tests/commands/browser/uploadFile.test.ts | 1 - .../commands/element/isDisplayed.test.ts | 1 - packages/webdriverio/tests/constants.test.ts | 1 - packages/webdriverio/tests/index.test.ts | 1 - packages/webdriverio/tests/module.test.ts | 23 - .../webdriverio/tests/multiremote.test.ts | 1 - .../tests/overwriteCommand.test.ts | 5 +- packages/webdriverio/tests/utils.test.ts | 8 - pnpm-lock.yaml | 198 +---- scripts/build.ts | 3 +- tests/package.json | 1 - tests/typings/devtools/async.ts | 17 - tests/typings/devtools/tsconfig.json | 10 - tests/typings/setup.js | 3 +- tests/typings/webdriverio/async.ts | 1 - tests/typings/webdriverio/tsconfig.json | 1 - vitest.config.ts | 4 - website/blog/2019-09-16-devtools.md | 6 + website/docs/AutomationProtocols.md | 187 +--- 166 files changed, 526 insertions(+), 9035 deletions(-) delete mode 100644 e2e/element.test.ts delete mode 100644 e2e/interop/devtools.e2e.js delete mode 100644 e2e/protocol.test.ts create mode 100644 e2e/screenshot.png delete mode 100644 e2e/standalone/edge.e2e.ts delete mode 100644 e2e/standalone/firefox.e2e.ts delete mode 100644 e2e/standalone/vitest.config.ts delete mode 100644 e2e/transitions.test.ts delete mode 100644 packages/devtools/.npmignore delete mode 100644 packages/devtools/LICENSE-MIT delete mode 100644 packages/devtools/README.md delete mode 100644 packages/devtools/package.json delete mode 100644 packages/devtools/src/cjs/index.ts delete mode 100644 packages/devtools/src/cjs/package.json delete mode 100644 packages/devtools/src/commands.ts delete mode 100644 packages/devtools/src/commands/acceptAlert.ts delete mode 100644 packages/devtools/src/commands/addCookie.ts delete mode 100644 packages/devtools/src/commands/back.ts delete mode 100644 packages/devtools/src/commands/closeWindow.ts delete mode 100644 packages/devtools/src/commands/createWindow.ts delete mode 100644 packages/devtools/src/commands/deleteAllCookies.ts delete mode 100644 packages/devtools/src/commands/deleteCookie.ts delete mode 100644 packages/devtools/src/commands/deleteSession.ts delete mode 100644 packages/devtools/src/commands/dismissAlert.ts delete mode 100644 packages/devtools/src/commands/elementClear.ts delete mode 100644 packages/devtools/src/commands/elementClick.ts delete mode 100644 packages/devtools/src/commands/elementSendKeys.ts delete mode 100644 packages/devtools/src/commands/executeAsyncScript.ts delete mode 100644 packages/devtools/src/commands/executeScript.ts delete mode 100644 packages/devtools/src/commands/findElement.ts delete mode 100644 packages/devtools/src/commands/findElementFromElement.ts delete mode 100644 packages/devtools/src/commands/findElementFromShadowRoot.ts delete mode 100644 packages/devtools/src/commands/findElements.ts delete mode 100644 packages/devtools/src/commands/findElementsFromElement.ts delete mode 100644 packages/devtools/src/commands/findElementsFromShadowRoot.ts delete mode 100644 packages/devtools/src/commands/forward.ts delete mode 100644 packages/devtools/src/commands/getActiveElement.ts delete mode 100644 packages/devtools/src/commands/getAlertText.ts delete mode 100644 packages/devtools/src/commands/getAllCookies.ts delete mode 100644 packages/devtools/src/commands/getElementAttribute.ts delete mode 100644 packages/devtools/src/commands/getElementCSSValue.ts delete mode 100644 packages/devtools/src/commands/getElementComputedLabel.ts delete mode 100644 packages/devtools/src/commands/getElementComputedRole.ts delete mode 100644 packages/devtools/src/commands/getElementProperty.ts delete mode 100644 packages/devtools/src/commands/getElementRect.ts delete mode 100644 packages/devtools/src/commands/getElementShadowRoot.ts delete mode 100644 packages/devtools/src/commands/getElementTagName.ts delete mode 100644 packages/devtools/src/commands/getElementText.ts delete mode 100644 packages/devtools/src/commands/getNamedCookie.ts delete mode 100644 packages/devtools/src/commands/getPageSource.ts delete mode 100644 packages/devtools/src/commands/getTimeouts.ts delete mode 100644 packages/devtools/src/commands/getTitle.ts delete mode 100644 packages/devtools/src/commands/getUrl.ts delete mode 100644 packages/devtools/src/commands/getWindowHandle.ts delete mode 100644 packages/devtools/src/commands/getWindowHandles.ts delete mode 100644 packages/devtools/src/commands/getWindowRect.ts delete mode 100644 packages/devtools/src/commands/index.ts delete mode 100644 packages/devtools/src/commands/isElementEnabled.ts delete mode 100644 packages/devtools/src/commands/isElementSelected.ts delete mode 100644 packages/devtools/src/commands/navigateTo.ts delete mode 100644 packages/devtools/src/commands/newSession.ts delete mode 100644 packages/devtools/src/commands/performActions.ts delete mode 100644 packages/devtools/src/commands/refresh.ts delete mode 100644 packages/devtools/src/commands/releaseActions.ts delete mode 100644 packages/devtools/src/commands/sendAlertText.ts delete mode 100644 packages/devtools/src/commands/setTimeouts.ts delete mode 100644 packages/devtools/src/commands/setWindowRect.ts delete mode 100644 packages/devtools/src/commands/status.ts delete mode 100644 packages/devtools/src/commands/switchToFrame.ts delete mode 100644 packages/devtools/src/commands/switchToParentFrame.ts delete mode 100644 packages/devtools/src/commands/switchToWindow.ts delete mode 100644 packages/devtools/src/commands/takeElementScreenshot.ts delete mode 100644 packages/devtools/src/commands/takeScreenshot.ts delete mode 100644 packages/devtools/src/constants.ts delete mode 100644 packages/devtools/src/devtoolsdriver.ts delete mode 100644 packages/devtools/src/elementstore.ts delete mode 100644 packages/devtools/src/finder/edge.ts delete mode 100644 packages/devtools/src/finder/finder.ts delete mode 100644 packages/devtools/src/finder/firefox.ts delete mode 100644 packages/devtools/src/finder/index.ts delete mode 100644 packages/devtools/src/index.ts delete mode 100644 packages/devtools/src/launcher.ts delete mode 100644 packages/devtools/src/scripts/.eslintrc.mjs delete mode 100644 packages/devtools/src/scripts/cleanUpSerializationSelector.ts delete mode 100644 packages/devtools/src/scripts/createWindow.ts delete mode 100644 packages/devtools/src/scripts/elementClear.ts delete mode 100644 packages/devtools/src/scripts/executeAsyncScript.ts delete mode 100644 packages/devtools/src/scripts/executeScript.ts delete mode 100644 packages/devtools/src/scripts/getActiveElement.ts delete mode 100644 packages/devtools/src/scripts/getElementAttribute.ts delete mode 100644 packages/devtools/src/scripts/getElementCSSValue.ts delete mode 100644 packages/devtools/src/scripts/getElementRect.ts delete mode 100644 packages/devtools/src/scripts/getElementTagName.ts delete mode 100644 packages/devtools/src/scripts/getElementText.ts delete mode 100644 packages/devtools/src/scripts/getUrl.ts delete mode 100644 packages/devtools/src/scripts/selectOption.ts delete mode 100644 packages/devtools/src/types.ts delete mode 100644 packages/devtools/src/utils.ts delete mode 100644 packages/devtools/tests/__snapshots__/devtools.test.ts.snap delete mode 100644 packages/devtools/tests/__snapshots__/devtoolsdriver.test.ts.snap delete mode 100644 packages/devtools/tests/__snapshots__/launcher.test.ts.snap delete mode 100644 packages/devtools/tests/__snapshots__/utils.test.ts.snap delete mode 100644 packages/devtools/tests/devtools.test.ts delete mode 100644 packages/devtools/tests/devtoolsdriver.test.ts delete mode 100644 packages/devtools/tests/elementStore.test.ts delete mode 100644 packages/devtools/tests/finder/finder.test.ts delete mode 100644 packages/devtools/tests/finder/systemProfiler.json delete mode 100644 packages/devtools/tests/index.test.ts delete mode 100644 packages/devtools/tests/launcher.test.ts delete mode 100644 packages/devtools/tests/utils.test.ts delete mode 100644 packages/devtools/tsconfig.json delete mode 100644 packages/devtools/tsconfig.prod.json create mode 100644 packages/wdio-types/src/Automation.ts delete mode 100644 tests/typings/devtools/async.ts delete mode 100644 tests/typings/devtools/tsconfig.json diff --git a/.gitignore b/.gitignore index 7fc901bed..bfe3e48e9 100644 --- a/.gitignore +++ b/.gitignore @@ -70,7 +70,6 @@ packages/**/cjs/ /*.js /*.mjs /*.ts -devtools.d.ts !.eslintrc.js !vitest.config.ts packages/**/package-lock.json diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 485f93fad..8a4a842fc 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -253,10 +253,7 @@ When a PR gets submitted, WebdriverIO runs the following checks: $ pnpm run test:smoke ``` - *e2e Tests* - Last but not least, we run actual e2e tests with a real browser to ensure that our WebDriver DevTools implementation is working as expected. These - tests spin up headless Chrome, Firefox and Edge browsers to test various implementations like for example the commands implemented in the `devtools` package. Given that the WebDriver functionality - is already tested with [WPT](https://github.com/web-platform-tests/wpt), we don't need to do it there. - In order to run these tests, an installation of [Firefox Nightly](https://www.mozilla.org/en-US/firefox/channel/desktop/#nightly), [Google Chrome](https://www.google.com/chrome/) and [Microsoft Edge](https://www.microsoft.com/en-us/edge/download)is required + Last but not least, we run actual e2e tests with a real headless browser to ensure we can spin up tests in all support variants and that certain services from end to end. You can manually trigger this check by calling: ```sh $ pnpm run test:e2e diff --git a/PROJECT_CHARTER.md b/PROJECT_CHARTER.md index 14a9f7107..a8d4249e4 100644 --- a/PROJECT_CHARTER.md +++ b/PROJECT_CHARTER.md @@ -14,10 +14,10 @@ With that, the project offers not only the ability of automating user agents usi - automation of modern web browsers - automation of mobile devices (web as well as native applications) +- automation of desktop applications (e.g. Electron or MacOS applications) - testing applications from end-to-end at scale (including parallelisation and reporting) - integration into 3rd party services and vendors (e.g. [Sauce Labs](https://saucelabs.com/) or [BrowserStack](https://browserstack.com/)) -- support of functional as well as non functional testing aspects (e.g. visual regression or frontend performance testing) -- automated debugging in the browser (e.g. using the [Chrome DevTools Protocol](https://chromedevtools.github.io/devtools-protocol/)) +- support of functional as well as non functional testing aspects (e.g. component testing, visual regression or frontend performance testing) - scraping of websites - providing technical content and best practices in form of [boilerplates](https://webdriver.io/) and [blog posts](https://webdriver.io/blog/) - providing technical help in form of a [community channel](https://discord.webdriver.io) diff --git a/README.md b/README.md index 757e25098..af0f93b31 100644 --- a/README.md +++ b/README.md @@ -48,7 +48,7 @@ *** -WebdriverIO is a test automation framework, for e2e as well as unit and component testing in the browser, that allows you to run tests based on the [WebDriver](https://w3c.github.io/webdriver/webdriver-spec.html), [WebDriver BiDi](https://github.com/w3c/webdriver-bidi) as well as [Chrome DevTools](https://chromedevtools.github.io/devtools-protocol/) protocol and [Appium](http://appium.io/) automation technology. It provides support for your favorite BDD/TDD test framework and will run your tests locally or in the cloud using Sauce Labs, BrowserStack, TestingBot or LambdaTest. +WebdriverIO is a test automation framework, for e2e as well as unit and component testing in the browser, that allows you to run tests based on the [WebDriver](https://w3c.github.io/webdriver/webdriver-spec.html) and [WebDriver BiDi](https://github.com/w3c/webdriver-bidi) as well as [Appium](http://appium.io/) automation technology. It provides support for your favorite BDD/TDD test framework and will run your tests locally or in the cloud using Sauce Labs, BrowserStack, TestingBot or LambdaTest. ## :woman_technologist: :man_technologist: Contributing Do you like WebdriverIO and want to help make it better? Awesome! Have a look into our [Contributor Documentation](CONTRIBUTING.md) to get started and find out what contributions can be and how to make them. @@ -88,7 +88,6 @@ __Did you build a WebdriverIO service or reporter?__ That's awesome! Please add ### Core - [webdriver](https://github.com/webdriverio/webdriverio/tree/main/packages/webdriver) - A Node.js bindings implementation for the W3C WebDriver and Mobile JSONWire Protocol -- [devtools](https://github.com/webdriverio/webdriverio/tree/main/packages/devtools) - A Chrome DevTools protocol binding that maps WebDriver commands into Chrome DevTools commands using Puppeteer - [webdriverio](https://github.com/webdriverio/webdriverio/blob/main/packages/webdriverio) - Next-gen browser and mobile automation test framework for Node.js - [@wdio/cli](https://github.com/webdriverio/webdriverio/tree/main/packages/wdio-cli) - A WebdriverIO testrunner command line interface diff --git a/__mocks__/@wdio/utils.ts b/__mocks__/@wdio/utils.ts index 0e5076717..56caa3d7f 100644 --- a/__mocks__/@wdio/utils.ts +++ b/__mocks__/@wdio/utils.ts @@ -10,8 +10,7 @@ import webdriverMonadOrig from '../../packages/wdio-utils/src/monad.js' import { isW3C as isW3cOrig, sessionEnvironmentDetector as sessionEnvDetector, - capabilitiesEnvironmentDetector as capabilitiesEnvDetector, - devtoolsEnvironmentDetector as devtoolsEnvDetector + capabilitiesEnvironmentDetector as capabilitiesEnvDetector } from '../../packages/wdio-utils/src/envDetector.js' import { UNICODE_CHARACTERS as UNICODE_CHARACTERS_ORIG, HOOK_DEFINITION as HOOK_DEFINITION_ORIG } from '../../packages/wdio-utils/src/constants.js' @@ -106,7 +105,6 @@ export const sessionEnvironmentDetector = vi.fn().mockImplementation( // @ts-ignore (...args) => sessionEnvDetector(...args)) export const capabilitiesEnvironmentDetector = capabilitiesEnvDetector -export const devtoolsEnvironmentDetector = devtoolsEnvDetector export const transformCommandLogResult = vi.fn().mockImplementation((data) => data) export const sleep = vi.fn().mockImplementation(sleepOrig) export const UNICODE_CHARACTERS = UNICODE_CHARACTERS_ORIG diff --git a/__mocks__/puppeteer-core.ts b/__mocks__/puppeteer-core.ts index fa2c9cfad..a8a27c9ee 100644 --- a/__mocks__/puppeteer-core.ts +++ b/__mocks__/puppeteer-core.ts @@ -43,6 +43,7 @@ const cdpSession = new CDPSessionMock() class TargetMock { page = vi.fn().mockImplementation(() => page) + asPage = vi.fn().mockImplementation(async () => page) createCDPSession = vi.fn().mockImplementation(() => cdpSession) } const target = new TargetMock() diff --git a/e2e/attach.test.ts b/e2e/attach.test.ts index 83f2e4eb1..57743975f 100644 --- a/e2e/attach.test.ts +++ b/e2e/attach.test.ts @@ -12,7 +12,9 @@ test('allow to attach to an existing session', async () => { const browser = await remote({ capabilities: { browserName: 'chrome', - 'wdio:devtoolsOptions': { headless: true, dumpio: true } + 'goog:chromeOptions': { + args: ['headless', 'disable-gpu'] + } } }) @@ -24,6 +26,9 @@ test('allow to attach to an existing session', async () => { await otherBrowser.deleteSession() + /** + * verify that browser session is deleted + */ const error = await browser.status().catch((err) => err) expect(error.message).not.toBe('ChromeDriver ready for new sessions.') expect(error.message).toEqual(expect.stringContaining('fetch failed')) diff --git a/e2e/element.test.ts b/e2e/element.test.ts deleted file mode 100644 index 863a3d994..000000000 --- a/e2e/element.test.ts +++ /dev/null @@ -1,316 +0,0 @@ -import { beforeAll, beforeEach, afterAll, describe, it, expect } from 'vitest' -import DevTools from '../packages/devtools/build/index.js' -import { ELEMENT_KEY } from '../packages/webdriver/build/constants.js' -import type { Client } from '../packages/devtools/build/index.js' - -let browser: Client - -beforeAll(async () => { - browser = await DevTools.newSession({ - outputDir: __dirname, - capabilities: { - browserName: 'chrome', - 'wdio:devtoolsOptions': { - headless: true - } - } - }) -}) - -describe('elements', () => { - beforeEach(async () => { - await browser.navigateTo('http://guinea-pig.webdriver.io') - }) - - it('should be able to do a drag&drop', async () => { - const drag = await browser.findElement('css selector', '.ui-draggable') - const dragPosition = await browser.getElementRect(drag[ELEMENT_KEY]) - const drop = await browser.findElement('css selector', '.ui-droppable') - const dropPosition = await browser.getElementRect(drop[ELEMENT_KEY]) - const dragStart = { - x: dragPosition.x + (dragPosition.width / 2), - y: dragPosition.y + (dragPosition.height / 2) - } - const dropEnd = { - x: (dropPosition.x + (dropPosition.width / 2) - dragStart.x), - y: (dropPosition.y + (dropPosition.height / 2) - dragStart.y) - } - - await browser.executeScript('window.scrollTo(0, 0)', []) - await browser.performActions([{ - type: 'pointer', - id: 'finger1', - parameters: { - pointerType: 'mouse' - }, - actions: [{ - type: 'pointerMove', - duration: 0, - ...dragStart - }, { - type: 'pointerDown', - button: 0 - }, { - type: 'pause', - duration: 10 - }, { - type: 'pointerMove', - duration: 100, - origin: 'pointer', - ...dropEnd - }, { - type: 'pointerUp', - button: 0 - }] - }]) - - const elem = await browser.findElement('css selector', '.searchinput') - expect(await browser.getElementProperty(elem[ELEMENT_KEY], 'value')) - .toBe('Dropped!') - }) - - it('findElement with css selector', async () => { - const yellowBox = await browser.findElement('css selector', '.yellow') - expect(yellowBox).toEqual({ [ELEMENT_KEY]: 'ELEMENT-4' }) - const redBox = await browser.findElement('css selector', '.red') - expect(redBox).toEqual({ [ELEMENT_KEY]: 'ELEMENT-5' }) - }) - - it('find nested element with css selector', async () => { - const nested = await browser.findElement('css selector', '.nested') - const header = await browser.findElementFromElement(nested[ELEMENT_KEY], 'css selector', '.findme') - expect(header).toEqual({ [ELEMENT_KEY]: 'ELEMENT-7' }) - }) - - it('findElement with xPath', async () => { - const xPathElement = await browser.findElement('xpath', '//*[@id="newWindow"]') - expect(xPathElement).toEqual({ [ELEMENT_KEY]: 'ELEMENT-8' }) - }) - - it('findElements with css selector', async () => { - const boxes = await browser.findElements('css selector', '.box') - expect(boxes).toHaveLength(5) - }) - - it('find nested elements with css selector', async () => { - const nested = await browser.findElement('css selector', '.nested') - const spans = await browser.findElementsFromElement(nested[ELEMENT_KEY], 'css selector', 'span') - expect(spans).toHaveLength(2) - }) - - it('find nested elements with xPath', async () => { - const nested = await browser.findElement('css selector', '.nested') - const spans = await browser.findElementsFromElement(nested[ELEMENT_KEY], 'xpath', './/div') - expect(spans).toHaveLength(1) - }) - - it('can click and go back and forward', async () => { - const link = await browser.findElement('css selector', '#secondPageLink') - await browser.elementClick(link[ELEMENT_KEY]) - expect(await browser.getTitle()).toBe('two') - await browser.back() - expect(await browser.getTitle()).toBe('WebdriverJS Testpage') - await browser.forward() - expect(await browser.getTitle()).toBe('two') - }) - - it('is able to select an option', async () => { - const option = await browser.findElement('css selector', 'option[value="someValue5"]') - await browser.elementClick(option[ELEMENT_KEY]) - const selectedValue = await browser.findElement('css selector', '#selectedValue') - expect(await browser.getElementText(selectedValue[ELEMENT_KEY])).toBe('someValue5') - }) - - it('element properties', async () => { - await browser.navigateTo('http://guinea-pig.webdriver.io') - const link = await browser.findElement('css selector', '#secondPageLink') - expect(await browser.getElementText(link[ELEMENT_KEY])).toBe('two') - expect(await browser.getElementAttribute(link[ELEMENT_KEY], 'href')).toBe('./two.html') - expect(await browser.getElementProperty(link[ELEMENT_KEY], 'tagName')).toBe('A') - expect(await browser.getElementTagName(link[ELEMENT_KEY])).toBe('a') - expect(await browser.getElementCSSValue(link[ELEMENT_KEY], 'color')).toBe('rgb(0, 136, 204)') - - const rect = await browser.getElementRect(link[ELEMENT_KEY]) - // can be 16 or 16.5 depending on the environment - expect(Math.floor(rect.height)).toBe(16) - expect(rect.width > 21 && rect.width < 24) // changes depending where it is run - expect(rect.x).toBe(15) - expect(Math.round(rect.y)).toBe(142) - - const selectedCheckbox = await browser.findElement('css selector', '.checkbox_selected') - expect(await browser.isElementSelected(selectedCheckbox[ELEMENT_KEY])).toBe(true) - const notSelectedCheckbox = await browser.findElement('css selector', '.checkbox_notselected') - expect(await browser.isElementSelected(notSelectedCheckbox[ELEMENT_KEY])).toBe(false) - - const disabledInput = await browser.findElement('css selector', 'input[value="d"]') - expect(await browser.isElementEnabled(disabledInput[ELEMENT_KEY])).toBe(false) - const enabledInput = await browser.findElement('css selector', 'input[value="a"]') - expect(await browser.isElementEnabled(enabledInput[ELEMENT_KEY])).toBe(true) - }) - - it('getActiveElement', async () => { - const textarea = await browser.findElement('css selector', 'textarea') - await browser.elementClick(textarea[ELEMENT_KEY]) - - const activeElement = await browser.getActiveElement() - expect(await browser.getElementTagName(activeElement[ELEMENT_KEY])).toBe('textarea') - }) - - it('elementSendKeys', async () => { - const textarea = await browser.findElement('css selector', 'textarea') - await browser.elementSendKeys(textarea[ELEMENT_KEY], 'foobar') - expect(await browser.getElementProperty(textarea[ELEMENT_KEY], 'value')).toBe('foobar') - }) - - it('elementSendKeys for file-type input', async () => { - const fileInput = await browser.findElement('css selector', '#upload-test') - await browser.elementSendKeys(fileInput[ELEMENT_KEY], 'README.md') - expect(await browser.getElementProperty(fileInput[ELEMENT_KEY], 'value')).toBe('C:\\fakepath\\README.md') - }) - - it('elementSendKeys can use special characters', async () => { - await browser.navigateTo('https://todomvc.com/examples/vue/dist/#/') - const todoInput = await browser.findElement('css selector', '.new-todo') - await browser.elementSendKeys(todoInput[ELEMENT_KEY], 'ToDo #1\uE007ToDo #2\uE007ToDo #3\uE007') - const todoCountElem = await browser.findElement('css selector', '.todo-count strong') - const todoCount = await browser.getElementText(todoCountElem[ELEMENT_KEY]) - expect(todoCount).toBe('3') - }) - - it('elementSendKeys can send unicode backspace', async () => { - const textarea = await browser.findElement('css selector', 'textarea') - await browser.elementSendKeys(textarea[ELEMENT_KEY], 'foobar') - expect(await browser.getElementProperty(textarea[ELEMENT_KEY], 'value')).toBe('foobar') - await browser.elementSendKeys(textarea[ELEMENT_KEY], '\uE003') - expect(await browser.getElementProperty(textarea[ELEMENT_KEY], 'value')).toBe('fooba') - }) - - it('elementClear', async () => { - const textarea = await browser.findElement('css selector', 'textarea') - await browser.elementClear(textarea[ELEMENT_KEY]) - expect(await browser.getElementProperty(textarea[ELEMENT_KEY], 'value')).toBe('') - }) - - it('link text', async () => { - const link = await browser.findElement('link text', 'two') - expect(await browser.getElementText(link[ELEMENT_KEY])).toBe('two') - }) - - it('partial link text', async () => { - const link = await browser.findElement('partial link text', 'new tab') - expect(await browser.getElementText(link[ELEMENT_KEY])).toBe('open new tab') - }) - - it('should be able to use keys command', async () => { - const textarea = await browser.findElement('css selector', 'textarea') - await browser.elementClick(textarea[ELEMENT_KEY]) - await browser.performActions([{ - type: 'key', - id: 'keyboard', - actions: [ - { type: 'keyDown', value: 'f' }, - { type: 'keyDown', value: 'o' }, - { type: 'keyDown', value: 'o' }, - { type: 'keyDown', value: 'b' }, - { type: 'keyDown', value: 'a' }, - { type: 'keyDown', value: 'r' }, - { type: 'keyDown', value: '😉' }, - { type: 'keyUp', value: 'f' }, - { type: 'keyUp', value: 'o' }, - { type: 'keyUp', value: 'o' }, - { type: 'keyUp', value: 'b' }, - { type: 'keyUp', value: 'a' }, - { type: 'keyUp', value: 'r' }, - { type: 'keyUp', value: '😉' } - ] - }]) - expect(await browser.getElementProperty(textarea[ELEMENT_KEY], 'value')) - .toBe('foobar😉') - }) - - it('should allow to click relative to the center of an element', async () => { - await browser.executeScript('window.scrollTo(0, 0)', []) - const message = await browser.findElement('css selector', '.btn1_right_clicked') - const btn2 = await browser.findElement('css selector', '.btn2') - - expect(await browser.getElementCSSValue(message[ELEMENT_KEY], 'display')) - .toBe('none') - await browser.performActions([{ - type: 'pointer', - id: 'pointer1', - parameters: { pointerType: 'mouse' }, - actions: [{ - type: 'pointerMove', - origin: { - [ELEMENT_KEY]: btn2[ELEMENT_KEY] - }, - x: -50, - y: 0 - }, { - type: 'pointerDown', - button: 2 - }, { - type: 'pointerUp', - button: 2 - }] - }]) - await new Promise((r) => setTimeout(r, 3000)) - expect(await browser.getElementCSSValue(message[ELEMENT_KEY], 'display')) - .toBe('block') - }) - - it('should allow to double click on an element', async () => { - await browser.navigateTo('http://guinea-pig.webdriver.io/') - const elem = await browser.findElement('css selector', '.btn1') - const notifier = await browser.findElement('css selector', '.btn1_dblclicked') - - expect(await browser.getElementAttribute(notifier[ELEMENT_KEY], 'style')) - .toBe(null) - - await browser.performActions([{ - type: 'pointer', - id: 'pointer1', - parameters: { pointerType: 'mouse' }, - actions: [ - { type: 'pointerMove', origin: elem, x: 0, y: 0 }, - { type: 'pointerDown', button: 0 }, - { type: 'pointerUp', button: 0 }, - { type: 'pause', duration: 10 }, - { type: 'pointerDown', button: 0 }, - { type: 'pointerUp', button: 0 } - ] - }]) - - expect(await browser.getElementAttribute(notifier[ELEMENT_KEY], 'style')) - .toBe('display: block;') - }) - - it('should support deep selectors', async () => { - await browser.navigateTo('https://polymer-library.polymer-project.org/3.0/api/elements/array-selector') - await new Promise((resolve) => setTimeout(resolve, 1000)) - const headerSlot = await browser.findElement('shadow', 'section[anchor-id="elementBase-properties"]') - expect( - (await browser.getElementText(headerSlot[ELEMENT_KEY])).trim() - ).toContain('Properties') - }) - - it('can fetch shadow elements', async () => { - await browser.navigateTo('https://polymer-library.polymer-project.org/3.0/api/elements/array-selector') - await new Promise((resolve) => setTimeout(resolve, 1000)) - const element = await browser.findElement('tag name', 'pw-footer') - const shadowRoot = await browser.getElementShadowRoot( - element['element-6066-11e4-a52e-4f735466cecf'] - ) - const elementRef = await browser.findElementFromShadowRoot( - shadowRoot['shadow-6066-11e4-a52e-4f735466cecf'], - 'css selector', - '.copyright' - ) - expect(await browser.getElementText(elementRef[ELEMENT_KEY])) - .toContain('Brought to you by The Polymer Project') - }) -}) - -afterAll(async () => { - await browser.deleteSession() -}) diff --git a/e2e/interop/devtools.e2e.js b/e2e/interop/devtools.e2e.js deleted file mode 100644 index 242e85948..000000000 --- a/e2e/interop/devtools.e2e.js +++ /dev/null @@ -1,18 +0,0 @@ -const assert = require('node:assert') -const Devtools = require('../../packages/devtools') - -;(async () => { - const client = await Devtools.newSession({ - capabilities: { - browserName: 'chrome', - 'goog:chromeOptions': { args: ['headless', 'disable-gpu'] } - } - }) - - await client.navigateTo('https://www.google.com/ncr') - assert.equal(await client.getTitle(), 'Google') - await client.deleteSession() -})().then( - () => console.log('Devtools CJS Test Passed!') || process.exit(0), - (err) => console.log('Devtools CJS Test Failed!', err) || process.exit(1) -) diff --git a/e2e/protocol.test.ts b/e2e/protocol.test.ts deleted file mode 100644 index b4f023089..000000000 --- a/e2e/protocol.test.ts +++ /dev/null @@ -1,399 +0,0 @@ -import { beforeAll, afterAll, describe, it, expect, vi } from 'vitest' - -import DevTools from '../packages/devtools/build/index.js' -import { ELEMENT_KEY } from '../packages/webdriver/build/constants.js' - -import type { Client } from '../packages/devtools/build/index.js' - -const sleep = (ms: number) => new Promise(resolve => setTimeout(resolve, ms)) - -let browser: Client - -async function waitForElement (browser: Client, selectorStrategy: string, selector: string, retries = 10): Promise { - const elem = await browser.findElement(selectorStrategy, selector) - if (elem[ELEMENT_KEY]) { - return elem[ELEMENT_KEY] - } - - if (retries < 0) { - throw new Error(`element with ${selectorStrategy} "${selector}" not found`) - } - - await sleep(200) - return waitForElement(browser, selectorStrategy, selector, --retries) -} - -beforeAll(async () => { - browser = await DevTools.newSession({ - outputDir: __dirname, - capabilities: { - browserName: 'chrome', - 'wdio:devtoolsOptions': { - headless: true - } - } - }) -}) - -it('should navigate to a page and get page info', async () => { - await browser.navigateTo('http://guinea-pig.webdriver.io') - expect(await browser.getTitle()).toBe('WebdriverJS Testpage') - expect(await browser.getUrl()).toContain('http://guinea-pig.webdriver.io') - expect(await browser.getPageSource()).toContain('WebdriverJS Testpage') - - const elem = await browser.findElement('css selector', '#t') - expect(await browser.getElementText(elem[ELEMENT_KEY])).toBe('lions, tigers') -}) - -it('should include the hash', async () => { - await browser.navigateTo('http://guinea-pig.webdriver.io#hash=hello') - expect(await browser.getUrl()).toBe('http://guinea-pig.webdriver.io/#hash=hello') -}) - -describe('timeouts', () => { - beforeAll(async () => { - await browser.navigateTo('http://guinea-pig.webdriver.io') - }) - - it('getTimeouts', async () => { - expect(await browser.getTimeouts()).toEqual({ - implicit: 0, - pageLoad: 300000, - script: 30000 - }) - }) - - it('setTimeouts', async () => { - await browser.setTimeouts(1, 2, 3) - expect(await browser.getTimeouts()).toEqual({ - implicit: 1, - pageLoad: 2, - script: 3 - }) - }) - - it('test script timeout', async () => { - expect.assertions(2) - - const asyncCommand = () => browser.executeAsyncScript( - 'return setTimeout(() => arguments[2](document.title + \' \' + arguments[0] + arguments[1]), 100)', - ['Test', '!']) - - try { - await asyncCommand() - } catch (err: any) { - expect(err.message).toBe('Evaluation failed: script timeout') - } - - await browser.setTimeouts(0, 1000, 300) - expect(await asyncCommand()).toBe('WebdriverJS Testpage Test!') - }) - - afterAll(async () => { - await browser.setTimeouts(0, 300000, 300000) - }) -}) - -describe('alerts', () => { - let jsAlertBtn: string, alertConfirmBtn: string, alertPromptBtn: string - - beforeAll(async () => { - await browser.navigateTo('https://the-internet.herokuapp.com/javascript_alerts') - const buttons = await browser.findElements('css selector', '.example button') - jsAlertBtn = buttons[0][ELEMENT_KEY] - alertConfirmBtn = buttons[1][ELEMENT_KEY] - alertPromptBtn = buttons[2][ELEMENT_KEY] - }) - - it('getAlertText', async () => { - await browser.elementClick(jsAlertBtn) - expect(await browser.getAlertText()).toBe('I am a JS Alert') - }) - - it('acceptAlert', async () => { - expect.assertions(2) - - await browser.acceptAlert() - - const result = await browser.findElement('css selector', '#result') - expect(await browser.getElementText(result[ELEMENT_KEY])) - .toBe('You successfully clicked an alert') - - try { - await browser.getAlertText() - } catch (err: any) { - expect(err.message).toBe('no such alert') - } - }) - - it('dismissAlert', async () => { - await browser.elementClick(alertConfirmBtn) - expect(await browser.getAlertText()).toBe('I am a JS Confirm') - - await browser.dismissAlert() - const result = await browser.findElement('css selector', '#result') - expect(await browser.getElementText(result[ELEMENT_KEY])) - .toBe('You clicked: Cancel') - - try { - await browser.getAlertText() - } catch (err: any) { - expect(err.message).toBe('no such alert') - } - }) - - it('confirmAlert', async () => { - await browser.elementClick(alertConfirmBtn) - expect(await browser.getAlertText()).toBe('I am a JS Confirm') - - await browser.acceptAlert() - const result = await browser.findElement('css selector', '#result') - expect(await browser.getElementText(result[ELEMENT_KEY])) - .toBe('You clicked: Ok') - }) - - it('sendAlertText', async () => { - await browser.elementClick(alertPromptBtn) - await browser.sendAlertText('foobar42') - const result = await browser.findElement('css selector', '#result') - expect(await browser.getElementText(result[ELEMENT_KEY])) - .toBe('You entered: foobar42') - - await browser.elementClick(alertPromptBtn) - await browser.dismissAlert() - const resultDismiss = await browser.findElement('css selector', '#result') - expect(await browser.getElementText(resultDismiss[ELEMENT_KEY])) - .toBe('You entered: null') - }) -}) - -describe('cookies', () => { - beforeAll(async () => { - await browser.navigateTo('http://guinea-pig.webdriver.io') - }) - - it('should have no cookies in the beginning', async () => { - expect(await browser.getAllCookies()).toEqual([]) - }) - - it('should add a cookie', async () => { - await browser.addCookie({ - name: 'foobar', - value: 42 - }) - await browser.navigateTo('http://guinea-pig.webdriver.io') - expect(await browser.getAllCookies()).toHaveLength(1) - - await browser.navigateTo('https://google.com') - const cookies = await browser.getAllCookies() - const ourCookie = cookies.find((cookie) => cookie.name ==='foobar' && cookie.value === '42') - expect(ourCookie).toBe(undefined) - }) - - it('deleteCookie', async () => { - await browser.navigateTo('http://guinea-pig.webdriver.io') - const cookie = await browser.getNamedCookie('foobar') - expect(cookie.value).toBe('42') - - await browser.deleteCookie('foobar') - await browser.navigateTo('http://guinea-pig.webdriver.io') - expect(await browser.getAllCookies()).toEqual([]) - }) -}) - -describe('window handling', () => { - beforeAll(async () => { - await browser.navigateTo('http://guinea-pig.webdriver.io') - }) - - it('should have a single window handle in the beginning', async () => { - const handle = await browser.getWindowHandle() - expect(typeof handle).toBe('string') - - const handles = await browser.getWindowHandles() - expect(handles).toEqual([handle]) - }) - - it('should switch to window created by DOM interaction', async () => { - const newWindowLink = await browser.findElement('css selector', '#newWindow') - await browser.elementClick(newWindowLink[ELEMENT_KEY]) - await sleep(1000) - expect(await browser.getWindowHandles()).toHaveLength(2) - - const handles = await browser.getWindowHandles() - await browser.switchToWindow(handles[1]) - expect(await browser.getTitle()).toBe('two') - - await browser.closeWindow() - expect(await browser.getTitle()).toBe('WebdriverJS Testpage') - expect(await browser.getWindowHandles()).toHaveLength(1) - }) - - it('createWindow', async () => { - expect(await browser.getTitle()).toBe('WebdriverJS Testpage') - - await browser.createWindow('tab') - const handles = await browser.getWindowHandles() - expect(handles).toHaveLength(2) - }) - - it('switchToWindow', async () => { - await browser.navigateTo('https://the-internet.herokuapp.com') - expect(await browser.getTitle()).toBe('The Internet') - - const handles = await browser.getWindowHandles() - await browser.switchToWindow(handles[0]) - expect(await browser.getTitle()).toBe('WebdriverJS Testpage') - }) - - it('closeWindow', async () => { - await browser.closeWindow() - expect(await browser.getTitle()).toBe('The Internet') - }) -}) - -describe('frames', () => { - beforeAll(async () => { - await browser.navigateTo('https://the-internet.herokuapp.com/iframe') - }) - - it('switchToFrame', async () => { - const getDocumentText = () => browser.executeScript( - 'return document.documentElement.outerText', - [] - ) - - expect(await getDocumentText()) - .toContain('An iFrame containing the TinyMCE WYSIWYG Editor') - const iframe = await browser.findElement('css selector', 'iframe') - await browser.switchToFrame(iframe) - - await sleep(1000) - expect(await getDocumentText()) - .toContain('Your content goes here.') - - const body = await browser.findElement('css selector', 'body') - expect(await browser.getElementAttribute(body[ELEMENT_KEY], 'id')).toBe('tinymce') - await browser.elementClick(body[ELEMENT_KEY]) - }) - - it('parentFrame', async () => { - await browser.switchToParentFrame() - expect(await browser.getTitle()).toBe('The Internet') - }) - - it('switchToFrame(number)', async () => { - const getDocumentText = () => browser.executeScript( - 'return document.documentElement.outerText', - [] - ) - - expect(await getDocumentText()) - .toContain('An iFrame containing the TinyMCE WYSIWYG Editor') - await browser.switchToFrame(0) - - expect(await getDocumentText()) - .toContain('Your content goes here.') - - const body = await browser.findElement('css selector', 'body') - expect(await browser.getElementAttribute(body[ELEMENT_KEY], 'id')).toBe('tinymce') - await browser.elementClick(body[ELEMENT_KEY]) - }) - - it('switchToFrame(null)', async () => { - await browser.switchToFrame(null) - expect(await browser.getTitle()).toBe('The Internet') - }) - - it('allows to switch to parent frame even if there isn\'t any', async () => { - await browser.navigateTo('http://guinea-pig.webdriver.io/two.html') - expect(await browser.getPageSource()).toContain('two') - await browser.switchToFrame(null) - expect(await browser.getPageSource()).toContain('two') - await browser.switchToFrame(null) - expect(await browser.getPageSource()).toContain('two') - }) -}) - -describe('executeScript', () => { - beforeAll(async () => { - await browser.navigateTo('http://guinea-pig.webdriver.io') - }) - - it('sync', async () => { - expect(await browser.executeScript( - 'return document.title + \' \' + arguments[0] + arguments[1]', - ['Test', '!']) - ).toBe('WebdriverJS Testpage Test!') - }) - - it('sync error', async () => { - const spy = vi.spyOn(global, 'clearTimeout') - spy.mockClear() - - await expect(async () => { - await browser.executeScript( - 'throw new Error(\'sync error\')', - [] - ) - }).rejects.toThrow('sync error') - - expect(spy).toHaveBeenCalledTimes(5) - spy.mockClear() - }) - - it('async', async () => { - expect(await browser.executeAsyncScript( - 'return setTimeout(() => arguments[2](document.title + \' \' + arguments[0] + arguments[1]), 500)', - ['Test2', '!']) - ).toBe('WebdriverJS Testpage Test2!') - }) - - it('respects promises', async () => { - expect(await browser.executeScript( - 'return new Promise((resolve) => setTimeout(() => resolve(document.title + \' \' + arguments[0] + arguments[1]), 500))', - ['Test3', '!']) - ).toBe('WebdriverJS Testpage Test3!') - }) - - it('refresh', async () => { - await browser.executeScript('window.foobar = 42', []) - expect(await browser.executeScript('return window.foobar', [])).toBe(42) - await browser.refresh() - expect(await browser.executeScript('return window.foobar', [])).toBe(undefined) - }) - - it('serializes elements', async () => { - const script = 'return (function () { return this.document.querySelector("body") }).apply(null, arguments)' - const elem = await browser.executeScript(script, []) - expect(typeof elem[ELEMENT_KEY]).toBe('string') - }) - - it('can handle various of script formats', async () => { - expect(await browser.executeScript('console.log("hello")', [])).toBe(undefined) - expect(await browser.executeScript(' return "string";', [])).toBe('string') - expect(await browser.executeScript('/* test */ console.log("test")', [])).toBe(undefined) - expect(await browser.executeScript('return { foo: "bar" }', [])).toEqual({ foo: 'bar' }) - expect(await browser.executeScript('return ({ foo: "bar" })', [])).toEqual({ foo: 'bar' }) - }) -}) - -describe('handles windows', () => { - it('should be able to work with popups', async () => { - await browser.navigateTo('http://guinea-pig.webdriver.io/') - const parentButton = await browser.findElement('css selector', '#parentButton') - await browser.elementClick(parentButton[ELEMENT_KEY]) - - const handles = await browser.getWindowHandles() - await browser.switchToWindow(handles[handles.length - 1]) - - const closeButton = await waitForElement(browser, 'css selector', '#closeButton') - await browser.elementClick(closeButton) - - expect(await browser.getTitle()).toBe('WebdriverJS Testpage') - }) -}) - -afterAll(async () => { - await browser.deleteSession() -}) diff --git a/e2e/screenshot.png b/e2e/screenshot.png new file mode 100644 index 0000000000000000000000000000000000000000..8f9714062697ee2e84ef0d5dba6fdafb68193098 GIT binary patch literal 34998 zcmeI4SyU6*zQ%*tA}U~1B!WQ&1P25srT}6qNTW1}%tII(xdMW8n5WR%jgc_42*Qye z2T-w@2azE_Py|#2WS&6?0x}w=K!9*|48eQvTKD0u)eq-%?|Q0LtW?$h_V@k%ds9{W z-c=nBIy9|a#=KsFJk1|^Fyo}R5%ABUwC zm5PeCCx_oZdtuwPhPhsxKo8GQ8)8NfwId~`^MeMt7b#?k+!_1Mt%hs%a78=rQ9u_% zNN>G$`uv`*N&c;>)}6XbDg!R>jCn~K-*0{;G4LhlM$jz*XU3VH1RI5|4_3D4 zD36j#I{WsN4(M&q`Ncyx6gALXlcG)B9qx7{C0VcP!o^ETw}XGxnJhg0Og!nIwzk%X z?2?kN)x1`J`f%k8+UMxYdb|BKqoqx0ueI@e{6hRIU|Wc97JfTe{bL&KhYcD z{IGSYf|mZI~DnKC(BQXRTA_bI&Zd%-(5uz&cNJ9M&2KgmvhvP zN^&_PPDnOFOa96$+_;l)O``#-N#iw1!lP?H_*mD8C7VeE2NTgdk)~kxNwlWfs*SLTgdzLcT zPGNe4imGB5bDwwDK0HA@s{hn|RFreNpaa1nvgM0nzVl;(cee0dmTfBYql;BwM&fZH z%M?ayHs)-FS-HQ@j{S-`LzXxk?rzxu&TkiO{)kn=T~}AP@c9(&)@dIjDDc9iS9rIi zHsQxOc|rQSckc%Od51sbBEOPEbvRmtvhqnb1vgUPSd1I7t!K=Srx<5jB9XLr$Fto> zWA>>9`h%;SoT98TpQWXR5C}KBUwHiYI}Hy>xC5FViEyG;hc$L|bPRIGDY%ALS67?l z+G|ADf?Z^m#*(XW6p|+l{3cD`FpuJP54r& zx7kEQ!KH^PkZqDH_UO^0elKP1#hF@JB5h%^n?7#I`Bl&wRzqoi zzTGNy?_x8F`COKs-(XJ>;{3T{%HX-3PI@puT9_2bxY6S`U3Rnnb6cjlb7$U@x)E?w zw6{{EsGjlGbYthfQlE%df5>1<)Eg32sR>?vH)zf^;pj34d zC2@Ydy{4{KX3=W+Hvxk5(Z-} zf9UV`o5;{jVQ&B>z7NH7J7BvhE4F&0eGY?3wt(Y@0k$y-d@PGJk+l8td0vGb1rKc0M9!N&3 zOoJxHyEkcly_BMxEzt|>`}v8Uv9XY{=LEs4KS1FeV>Cr^?!!fD1-C%xY=fFwy~^x+ zl&Y^gaqSSNC%>#V4)dB|OWf}rx)>a_61HFIw4`q0{_R3C&Zj7Od8Z#ro9-gvnLcr{ z_Bq5O&0jd*BM|IsA6AWLQ!L{Naz78f@n$ZK5SQ95{cgHX z_o<4BxfBfqdyi)pUhX@EXRd(WIX*n`me-^?RX2h_0C%axulO;DKv)}>D5H^}tO z4n5GlLg6tC&Nn=;^{2c{ODbicSstWQwZRDW%?#%LES@A_Ou?;~*JO5^k{2q3w3b{H8)ZdjAn`>)R;v>44}i_jhbCSH;ChE>vpA} zM-u&9(i_Ic#=2BKIgaR#PKFis2a>nuqyghEJ0y4*xt(rPo-S=3CId7sjIENF`N@f9 ztq-x>8Q{Pm^UZy6A`L*RxZA^%?*m7Qpy?QS?hIGlY@I^Q;TVg&X|LI^7dn|3&=evK z@ascX!Ggxl1rl@ibynM11qC7M%*>2!Pl{#)!Bs5xM)T82&je#jpTlLkoHKqTM{mm! z6BG6S^76l&oqGz&one&%YNe={=xC2ovR8qVMS&FSo=FEXGBQFx)y7Oy6E;RD=%Z-s zy4X{8{Zo(8o~uVGpdLN0?57lZ@)aICb}YH11aoeAB7e0+igUGt*`;V)xq`fN$JI~} zACUx%N7tl77elFXHL19`SmG6b>1kmkM2a%|H_^MTtVP;mtbAbw^D(7yqJaXke`BJ9otA z1E`ZNx5N;){QdHB|C%T_4}WNlsGl^e6MwGZT&cn_B}OTuQntY8F~sTgw{5oEQcfE<@2Ly9-hM0rcKn z5Rt)>7cb-!eaHTYRl^6iw6<2oE8&upk`C!$K0oewenVfIbgry-d1EY#G4hF#!P-uA z3+p_XU3dZko} zA`IwdyEnfds+zC(g_~ML4L_dc@IDqPBJX@83p8Dn-s@#`R$jikSyv5hR~>q^Q{T|g z&;nN{=ho*lQZTL@iaM42lUmB9uw%T{$BrL2SJ!KP`s49p(UaEa-zG+=%)DV$LpN1` zxJ7`Nknxa^5R{ywfp!8$PEscx1?qldOUoW=O+>`-@UZ9P#*l8*+uj@)LeJ1}KJx*K z=0&5)#Bz`NGYu;GKQ}dJl`|RRC@?ybHhX>q;HqmZzBAvU=4fjCuGILSFQ+#MoiN)2 zhJ9E!Rj;WY)S2trQ87Yyy*2dp^;Lic+*mj$SUw51W!v}ePHubBFt9X59-JNn}~L~8cqv{yzo)j~iSf1$X~=`{gK z130V(!#_HG!82VmLiCj9M8{}91)pJ>FKLu%cDR%?&j?!EwKo54W>Uet))%hCxeDU> z1voiDz?mD%4{%xwM1YfWAOif41rPy30f+#hz!CvM0f+#h07QULVC4ah6o3d23P1!1 z1t0>10ubT<0fnG7yY}#}kHoetnK#&Yxou|sUVGEV)3XYRjsLR2!z^vU1~p;p01dIO z!(ZGG!bV{L5$tyX{vIuct%Q`@c-#W3Av+F#amvBW!_33Xvr+(o2Z0ACIqUTRreR$O zSpL_AfOv#>{1fo_It%^vz4RS3g^86@gIj^9l3W{as$1{+_L4h1%r+d8h`>21rP-g1rP;)CJKLl;C1ts8Nc7H-m)&mX=0S$qGX@F^fC1c$KFby0b zU~Pc40j2?_0Vn`b08s!@08s!@u(8-CoHVV+;qO)(c9K26uF87yO}{TcnJ6Bl^=;MF z*v;P-Y=wu}27#Keb$~uu2L?0*Gz0>s0j2?gfGB_{_;*6VM;~4A@RYqS035%uy$Lf4 zGy3n8QJ71ZOE_>~djRHgT?c50zkwla|L4MPa2+>xpTn=W**d__`PUf$4FL`DCw$Hi zM1+xok%Fryt$P5bfg=PgFl;3t9w8nf9ywHiX@F^fX@F?}3P2S6=cC~KWLGY$6%_~Z z<4O}U;2UZ72EPer>i|Cz|7#n { - browser = await DevTools.newSession({ - outputDir: __dirname, - capabilities: { - browserName: 'edge', - 'wdio:devtoolsOptions': { - dumpio: true, - headless: true - } - } - }) -}) - -describe('Chromium Edge', () => { - it('can browser pages', async () => { - await browser.navigateTo('http://json.org/') - expect(await browser.getTitle()).toBe('JSON') - }) - - it('has proper UA', async () => { - const userAgent = await browser.executeScript('return navigator.userAgent', []) - console.log('Detected user agent:', userAgent) - expect(userAgent).toContain('Edg/') - }) -}) - -afterAll(async () => { - if (!browser) { - return - } - await browser.deleteSession() -}) diff --git a/e2e/standalone/firefox.e2e.ts b/e2e/standalone/firefox.e2e.ts deleted file mode 100644 index 201dc26d6..000000000 --- a/e2e/standalone/firefox.e2e.ts +++ /dev/null @@ -1,40 +0,0 @@ -import { beforeAll, describe, it, afterAll, expect } from 'vitest' -import DevTools, { type Client } from '../../packages/devtools/src/index.js' - -let browser: Client - -beforeAll(async () => { - browser = await DevTools.newSession({ - capabilities: { - browserName: 'firefox', - 'wdio:devtoolsOptions': { - dumpio: true, - headless: true - }, - ...(process.env.FIREFOX_BINARY - ? { 'moz:firefoxOptions': { binary: process.env.FIREFOX_BINARY } } - : {} - ) - } - }) -}) - -describe('Firefox', () => { - it('can browser pages', async () => { - await browser.navigateTo('http://json.org/') - expect(await browser.getTitle()).toBe('JSON') - }) - - it('has proper UA', async () => { - const userAgent = await browser.executeScript('return navigator.userAgent', []) - console.log('Detected user agent:', userAgent) - expect(userAgent).toContain('Firefox') - }) -}) - -afterAll(async () => { - if (!browser) { - return - } - await browser.deleteSession() -}) diff --git a/e2e/standalone/vitest.config.ts b/e2e/standalone/vitest.config.ts deleted file mode 100644 index 980fd80f9..000000000 --- a/e2e/standalone/vitest.config.ts +++ /dev/null @@ -1,11 +0,0 @@ -/// -import { defineConfig } from 'vite' - -export default defineConfig({ - test: { - testTimeout: 1000 * 60, - include: ['./**/*.e2e.ts'], - hookTimeout: 60 * 1000, - threads: false - } -}) diff --git a/e2e/transitions.test.ts b/e2e/transitions.test.ts deleted file mode 100644 index ca07f6209..000000000 --- a/e2e/transitions.test.ts +++ /dev/null @@ -1,64 +0,0 @@ -import { beforeAll, afterAll, test, expect } from 'vitest' -import DevTools from '../packages/devtools/build/index.js' -import { ELEMENT_KEY } from '../packages/webdriver/build/constants.js' -import type { Client } from '../packages/devtools/build/index.js' - -let browser: Client - -beforeAll(async () => { - browser = await DevTools.newSession({ - outputDir: __dirname, - capabilities: { - browserName: 'chrome', - 'wdio:devtoolsOptions': { - headless: true - } - } - }) -}) - -test('can handle navigation through clicks', async () => { - await browser.navigateTo('http://guinea-pig.webdriver.io') - expect(await browser.getTitle()).toBe('WebdriverJS Testpage') - - const link = await browser.findElement('link text', 'two') - await browser.elementClick(link[ELEMENT_KEY]) - expect(await browser.getTitle()).toBe('two') - - await browser.back() - expect(await browser.getTitle()).toBe('WebdriverJS Testpage') - - const githubLink = await browser.findElement('link text', 'GitHub Repo') - await browser.elementClick(githubLink[ELEMENT_KEY]) - expect(await browser.getTitle()).toContain('GitHub') -}) - -test('can handle navigation via JS', async () => { - await browser.navigateTo('http://guinea-pig.webdriver.io') - expect(await browser.getTitle()).toBe('WebdriverJS Testpage') - - await browser.executeScript('window.location.href = "https://github.com"', []) - expect(await browser.getTitle()).toContain('GitHub') -}) - -test('can handle random page transitions', async () => { - await browser.navigateTo('http://guinea-pig.webdriver.io') - await browser.executeScript('return (() => setTimeout(() => { window.location.href = "https://github.com" }, 200))()', []) - - const start = Date.now() - while (Date.now() - start < 1000) { - const title = await browser.getTitle() - expect(typeof title).toBe('string') - - /** - * stop loop if new page was reached - */ - if (title.includes('GitHub')) { - break - } - } -}) - -afterAll(async () => { - await browser.deleteSession() -}) diff --git a/e2e/wdio/headless/secondTest.e2e.ts b/e2e/wdio/headless/secondTest.e2e.ts index 75293667d..ea9936b0c 100644 --- a/e2e/wdio/headless/secondTest.e2e.ts +++ b/e2e/wdio/headless/secondTest.e2e.ts @@ -8,5 +8,5 @@ describe('main suite 1', () => { : caps.browserName && caps.browserName.includes('chrome') ? 'chrome/' : caps.browserName!.toLowerCase() await browser.url('http://guinea-pig.webdriver.io/') await expect($('#useragent')).toHaveTextContaining(assertionValue, { ignoreCase: true }) - }) + }, { retry: 2 }) }) diff --git a/package.json b/package.json index 4880ebff8..e147d5977 100644 --- a/package.json +++ b/package.json @@ -46,11 +46,9 @@ "test:component:run": "cd e2e && pnpm test:browser", "test:smoke": "node ./tests/smoke.runner.js", "test:e2e": "vitest --config e2e/vitest.config.ts --run", - "test:e2e:edge": "vitest --config e2e/standalone/vitest.config.ts edge.e2e.ts", - "test:e2e:firefox": "vitest --config e2e/standalone/vitest.config.ts firefox.e2e.ts", "test:e2e:webdriver": "tsx ./packages/wdio-cli/bin/wdio.js ./e2e/wdio/wdio.local.conf.ts", "test:e2e:cloud": "tsx ./packages/wdio-cli/bin/wdio.js ./e2e/wdio/wdio.sauce.conf.ts", - "test:e2e:cjs": "node e2e/interop/webdriverio.e2e.js && node e2e/interop/webdriver.e2e.js && node e2e/interop/devtools.e2e.js && node e2e/interop/cucumber-framework.e2e.js && node e2e/interop/allure-reporter.e2e.js && node e2e/interop/shared-store.e2e.js && node e2e/interop/wdio-cli.e2e.js && node e2e/interop/wdio-reporter.e2e.js", + "test:e2e:cjs": "node e2e/interop/webdriverio.e2e.js && node e2e/interop/webdriver.e2e.js && node e2e/interop/cucumber-framework.e2e.js && node e2e/interop/allure-reporter.e2e.js && node e2e/interop/shared-store.e2e.js && node e2e/interop/wdio-cli.e2e.js && node e2e/interop/wdio-reporter.e2e.js", "ts": "run-p ts:*", "ts:webdriver": "cd tests/typings/webdriver && npx tsc --skipLibCheck", "ts:devtools": "cd tests/typings/devtools && npx tsc --skipLibCheck", diff --git a/packages/devtools/.npmignore b/packages/devtools/.npmignore deleted file mode 100644 index 63f204227..000000000 --- a/packages/devtools/.npmignore +++ /dev/null @@ -1,4 +0,0 @@ -src -tests -tsconfig.json -tsconfig.prod.json diff --git a/packages/devtools/LICENSE-MIT b/packages/devtools/LICENSE-MIT deleted file mode 100644 index 206658030..000000000 --- a/packages/devtools/LICENSE-MIT +++ /dev/null @@ -1,20 +0,0 @@ -Copyright (c) OpenJS Foundation and other contributors - -Permission is hereby granted, free of charge, to any person obtaining -a copy of this software and associated documentation files (the -'Software'), to deal in the Software without restriction, including -without limitation the rights to use, copy, modify, merge, publish, -distribute, sublicense, and/or sell copies of the Software, and to -permit persons to whom the Software is furnished to do so, subject to -the following conditions: - -The above copyright notice and this permission notice shall be -included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. -IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY -CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, -TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE -SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/packages/devtools/README.md b/packages/devtools/README.md deleted file mode 100644 index e737deb88..000000000 --- a/packages/devtools/README.md +++ /dev/null @@ -1,157 +0,0 @@ -> [!WARNING] -> ***WebdriverIO is deprecating the use of Chrome Devtools as automation protocol through a WebDriver like interface. Instead, you can use the [`getPuppeteer`](/docs/api/browser/getPuppeteer) command to get ahold of a Puppeteer instance for Chrome Devtools introspection and automation.*** - -DevTools -======== - -> A Chrome DevTools protocol binding that maps WebDriver commands into Chrome DevTools commands using [Puppeteer](https://www.npmjs.com/package/puppeteer) - -This package provides a low level interface to run browser automation scripts based on the WebDriver protocol. If you are looking for a tool to automate Chrome or Firefox you should look up [Puppeteer](https://www.npmjs.com/package/puppeteer). This is suppose to be used by the [WebdriverIO](https://webdriver.io/) package in order to run its automation on the Chrome DevTools protocol. - -## Install - -```sh -npm i webdriverio -``` - -## Example - -The following example demonstrates how WebdriverIO can be used with the `devtools` package as automation binding using the [`automationProtocol`](https://webdriver.io/docs/options#automationProtocol) option: - -```js -import { remote } from 'webdriverio' - -const browser = await remote({ - automationProtocol: 'devtools', - capabilities: { - browserName: 'chrome', - 'wdio:devtoolsOptions': { - headless: true - } - } -}) - -await browser.url('https://webdriver.io') - -/** - * run Puppeteer code - */ -const puppeteer = await browser.getPuppeteer() -const page = (await puppeteer.pages())[0] -await page.setRequestInterception(true) -page.on('request', interceptedRequest => { - if (interceptedRequest.url().endsWith('webdriverio.png')) { - return interceptedRequest.continue({ - url: 'https://user-images.githubusercontent.com/10379601/29446482-04f7036a-841f-11e7-9872-91d1fc2ea683.png' - }) - } - - interceptedRequest.continue() -}) - -// continue with WebDriver commands -await browser.url('https://v6.webdriver.io') -await browser.pause(2000) - -/** - * now on the https://v6.webdriver.io page you see the Puppeteer logo - * instead of the WebdriverIO one - */ - -await browser.deleteSession() -``` - -## `wdio:devtoolsOptions` Capability - -In order to set [Puppeteer specific configurations](https://pptr.dev/#?product=Puppeteer&version=v5.5.0&show=api-puppeteerlaunchoptions) you can use the `wdio:devtoolsOptions` capability which is a custom property (compliant to the WebDriver protocol), e.g.: - -```js -{ - browserName: 'chrome', - 'wdio:devtoolsOptions': { - headless: true, - defaultViewport: { - width: 800, - height: 600, - deviceScaleFactor: 1, - isMobile: false - } - } -} -``` - -### Commands - -The following commands are already supported: - -- [x] [newSession](https://w3c.github.io/webdriver/#new-session) -- [x] [deleteSession](https://w3c.github.io/webdriver/#delete-session) -- [x] [status](https://w3c.github.io/webdriver/#status) -- [x] [getTimeouts](https://w3c.github.io/webdriver/#get-timeouts) -- [x] [setTimeouts](https://w3c.github.io/webdriver/#set-timeouts) -- [x] [getUrl](https://w3c.github.io/webdriver/#get-current-url) -- [x] [navigateTo](https://w3c.github.io/webdriver/#navigate-to) -- [x] [back](https://w3c.github.io/webdriver/#back) -- [x] [forward](https://w3c.github.io/webdriver/#forward) -- [x] [refresh](https://w3c.github.io/webdriver/#refresh) -- [x] [getTitle](https://w3c.github.io/webdriver/#get-title) -- [x] [getWindowHandle](https://w3c.github.io/webdriver/#get-window-handle) -- [x] [closeWindow](https://w3c.github.io/webdriver/#close-window) -- [x] [switchToWindow](https://w3c.github.io/webdriver/#switch-to-window) -- [x] [createWindow](https://w3c.github.io/webdriver/#new-window) -- [x] [getWindowHandles](https://w3c.github.io/webdriver/#get-window-handles) -- [x] [switchToFrame](https://w3c.github.io/webdriver/#switch-to-frame) -- [x] [switchToParentFrame](https://w3c.github.io/webdriver/#switch-to-parent-frame) -- [x] [getWindowRect](https://w3c.github.io/webdriver/#get-window-rect) -- [x] [setWindowRect](https://w3c.github.io/webdriver/#set-window-rect) -- [ ] [maximizeWindow](https://w3c.github.io/webdriver/#maximize-window) (not possible with Puppeteer) -- [ ] [minimizeWindow](https://w3c.github.io/webdriver/#minimize-window) (not possible with Puppeteer) -- [ ] [fullscreenWindow](https://w3c.github.io/webdriver/#fullscreen-window) (not possible with Puppeteer) -- [x] [findElement](https://w3c.github.io/webdriver/#find-element) -- [x] [findElements](https://w3c.github.io/webdriver/#find-elements) -- [x] [findElementFromElement](https://w3c.github.io/webdriver/#find-element-from-element) -- [x] [findElementsFromElement](https://w3c.github.io/webdriver/#find-elements-from-element) -- [x] [getActiveElement](https://w3c.github.io/webdriver/#get-active-element) -- [x] [isElementSelected](https://w3c.github.io/webdriver/#is-element-selected) -- [x] [isElementDisplayed](https://w3c.github.io/webdriver/#element-displayedness) -- [x] [getElementAttribute](https://w3c.github.io/webdriver/#get-element-attribute) -- [x] [getElementProperty](https://w3c.github.io/webdriver/#get-element-property) -- [x] [getElementCSSValue](https://w3c.github.io/webdriver/#get-element-css-value) -- [x] [getElementText](https://w3c.github.io/webdriver/#get-element-text) -- [x] [getElementTagName](https://w3c.github.io/webdriver/#get-element-tag-name) -- [x] [getElementRect](https://w3c.github.io/webdriver/#get-element-rect) -- [x] [isElementEnabled](https://w3c.github.io/webdriver/#is-element-enabled) -- [x] [elementClick](https://w3c.github.io/webdriver/#element-click) -- [x] [elementClear](https://w3c.github.io/webdriver/#element-clear) -- [x] [elementSendKeys](https://w3c.github.io/webdriver/#element-send-keys) -- [x] [getPageSource](https://w3c.github.io/webdriver/#get-page-source) -- [x] [executeScript](https://w3c.github.io/webdriver/#execute-script) -- [x] [executeAsyncScript](https://w3c.github.io/webdriver/#execute-async-script) -- [x] [getAllCookies](https://w3c.github.io/webdriver/#get-all-cookies) -- [x] [getNamedCookie](https://w3c.github.io/webdriver/#get-named-cookie) -- [x] [addCookie](https://w3c.github.io/webdriver/#add-cookie) -- [x] [deleteAllCookies](https://w3c.github.io/webdriver/#delete-all-cookies) -- [x] [deleteCookie](https://w3c.github.io/webdriver/#delete-cookie) -- [x] [performActions](https://w3c.github.io/webdriver/#perform-actions) -- [x] [releaseActions](https://w3c.github.io/webdriver/#release-actions) -- [x] [dismissAlert](https://w3c.github.io/webdriver/#dismiss-alert) -- [x] [acceptAlert](https://w3c.github.io/webdriver/#accept-alert) -- [x] [getAlertText](https://w3c.github.io/webdriver/#get-alert-text) -- [x] [sendAlertText](https://w3c.github.io/webdriver/#send-alert-text) -- [x] [takeScreenshot](https://w3c.github.io/webdriver/#take-screenshot) -- [x] [takeElementScreenshot](https://w3c.github.io/webdriver/#take-element-screenshot) - -### Selector Strategies - -- [x] [CSS Selector](https://w3c.github.io/webdriver/#css-selectors) -- [x] [Link Text](https://w3c.github.io/webdriver/#partial-link-text) -- [x] [Partial Link Text](https://w3c.github.io/webdriver/#partial-link-text) -- [x] [Tag Name](https://w3c.github.io/webdriver/#tag-name) -- [x] [XPath](https://w3c.github.io/webdriver/#xpath) - -### Browser - -- [x] Chrome -- [x] Firefox (nightly only) -- [x] Edge -- [ ] Safari diff --git a/packages/devtools/package.json b/packages/devtools/package.json deleted file mode 100644 index 77dbf9edc..000000000 --- a/packages/devtools/package.json +++ /dev/null @@ -1,62 +0,0 @@ -{ - "name": "devtools", - "version": "8.32.1", - "description": "A Chrome DevTools protocol binding that maps WebDriver commands into Chrome DevTools commands using Puppeteer", - "author": "Christian Bromann ", - "homepage": "https://github.com/webdriverio/webdriverio/tree/main/packages/devtools", - "license": "MIT", - "engines": { - "node": ">=18" - }, - "repository": { - "type": "git", - "url": "git://github.com/webdriverio/webdriverio.git", - "directory": "packages/devtools" - }, - "keywords": [ - "devtoolsdriver" - ], - "bugs": { - "url": "https://github.com/webdriverio/webdriverio/issues" - }, - "type": "module", - "main": "./build/cjs/index.js", - "module": "./build/index.js", - "types": "./build/index.d.ts", - "exports": { - ".": { - "import": { - "types": "./build/index.d.ts", - "default": "./build/index.js" - }, - "require": { - "types": "./build/index.d.ts", - "default": "./build/cjs/index.js" - } - }, - "./package.json": "./package.json" - }, - "typeScriptVersion": "3.8.3", - "dependencies": { - "@types/node": "^20.1.0", - "@wdio/config": "8.31.1", - "@wdio/logger": "8.28.0", - "@wdio/protocols": "8.32.0", - "@wdio/types": "8.31.1", - "@wdio/utils": "8.31.1", - "chrome-launcher": "^1.0.0", - "edge-paths": "^3.0.5", - "import-meta-resolve": "^4.0.0", - "puppeteer-core": "22.3.0", - "query-selector-shadow-dom": "^1.0.0", - "ua-parser-js": "^1.0.37", - "uuid": "^9.0.0", - "which": "^4.0.0" - }, - "devDependencies": { - "@types/ua-parser-js": "^0.7.33", - "@types/uuid": "^9.0.0", - "@types/which": "^3.0.3", - "@wdio/globals": "8.32.1" - } -} diff --git a/packages/devtools/src/cjs/index.ts b/packages/devtools/src/cjs/index.ts deleted file mode 100644 index b98728e20..000000000 --- a/packages/devtools/src/cjs/index.ts +++ /dev/null @@ -1,42 +0,0 @@ -class Devtools { - static async newSession ( - options: any, - modifier?: (...args: any[]) => any, - userPrototype = {}, - customCommandWrapper?: (...args: any[]) => any - ): Promise { - const WebDriver = (await import('../index.js')).default - return WebDriver.newSession(options, modifier, userPrototype, customCommandWrapper) - } - - /** - * allows user to attach to existing sessions - */ - static async attachToSession ( - options: any, - modifier?: (...args: any[]) => any, - userPrototype = {}, - commandWrapper?: (...args: any[]) => any - ): Promise { - const Devtools = (await import('../index.js')).default - return Devtools.attachToSession(options, modifier, userPrototype, commandWrapper) - } - - /** - * Changes The instance session id and browser capabilities for the new session - * directly into the passed in browser object - * - * @param {Object} instance the object we get from a new browser session. - * @returns {string} the new session id of the browser - */ - static async reloadSession (instance: any, newCapabilities: any) { - const Devtools = (await import('../index.js')).default - return Devtools.reloadSession(instance, newCapabilities) - } - - static get Devtools () { - return Devtools - } -} - -module.exports = Devtools diff --git a/packages/devtools/src/cjs/package.json b/packages/devtools/src/cjs/package.json deleted file mode 100644 index 5bbefffba..000000000 --- a/packages/devtools/src/cjs/package.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "type": "commonjs" -} diff --git a/packages/devtools/src/commands.ts b/packages/devtools/src/commands.ts deleted file mode 100644 index f986c2d3a..000000000 --- a/packages/devtools/src/commands.ts +++ /dev/null @@ -1,69 +0,0 @@ -import acceptAlert from './commands/acceptAlert.js' -import addCookie from './commands/addCookie.js' -import back from './commands/back.js' -import closeWindow from './commands/closeWindow.js' -import createWindow from './commands/createWindow.js' -import deleteAllCookies from './commands/deleteAllCookies.js' -import deleteCookie from './commands/deleteCookie.js' -import deleteSession from './commands/deleteSession.js' -import dismissAlert from './commands/dismissAlert.js' -import elementClear from './commands/elementClear.js' -import elementClick from './commands/elementClick.js' -import elementSendKeys from './commands/elementSendKeys.js' -import executeAsyncScript from './commands/executeAsyncScript.js' -import executeScript from './commands/executeScript.js' -import findElement from './commands/findElement.js' -import findElementFromElement from './commands/findElementFromElement.js' -import findElements from './commands/findElements.js' -import findElementsFromElement from './commands/findElementsFromElement.js' -import getElementShadowRoot from './commands/getElementShadowRoot.js' -import findElementFromShadowRoot from './commands/findElementFromShadowRoot.js' -import findElementsFromShadowRoot from './commands/findElementsFromShadowRoot.js' -import forward from './commands/forward.js' -import getActiveElement from './commands/getActiveElement.js' -import getAlertText from './commands/getAlertText.js' -import getAllCookies from './commands/getAllCookies.js' -import getElementAttribute from './commands/getElementAttribute.js' -import getElementCSSValue from './commands/getElementCSSValue.js' -import getElementComputedLabel from './commands/getElementComputedLabel.js' -import getElementComputedRole from './commands/getElementComputedRole.js' -import getElementProperty from './commands/getElementProperty.js' -import getElementRect from './commands/getElementRect.js' -import getElementTagName from './commands/getElementTagName.js' -import getElementText from './commands/getElementText.js' -import getNamedCookie from './commands/getNamedCookie.js' -import getPageSource from './commands/getPageSource.js' -import getTimeouts from './commands/getTimeouts.js' -import getTitle from './commands/getTitle.js' -import getUrl from './commands/getUrl.js' -import getWindowHandle from './commands/getWindowHandle.js' -import getWindowHandles from './commands/getWindowHandles.js' -import getWindowRect from './commands/getWindowRect.js' -import isElementEnabled from './commands/isElementEnabled.js' -import isElementSelected from './commands/isElementSelected.js' -import navigateTo from './commands/navigateTo.js' -import newSession from './commands/newSession.js' -import performActions from './commands/performActions.js' -import refresh from './commands/refresh.js' -import releaseActions from './commands/releaseActions.js' -import sendAlertText from './commands/sendAlertText.js' -import setTimeouts from './commands/setTimeouts.js' -import setWindowRect from './commands/setWindowRect.js' -import status from './commands/status.js' -import switchToFrame from './commands/switchToFrame.js' -import switchToParentFrame from './commands/switchToParentFrame.js' -import switchToWindow from './commands/switchToWindow.js' -import takeElementScreenshot from './commands/takeElementScreenshot.js' -import takeScreenshot from './commands/takeScreenshot.js' - -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, getElementShadowRoot, findElementFromShadowRoot, findElementsFromShadowRoot -} diff --git a/packages/devtools/src/commands/acceptAlert.ts b/packages/devtools/src/commands/acceptAlert.ts deleted file mode 100644 index 8831f8238..000000000 --- a/packages/devtools/src/commands/acceptAlert.ts +++ /dev/null @@ -1,17 +0,0 @@ -import type DevToolsDriver from '../devtoolsdriver.js' - -/** - * The Accept Alert command accepts a simple dialog if present, otherwise error. - * - * @alias browser.acceptAlert - * @see https://w3c.github.io/webdriver/#dfn-accept-alert - */ -export default async function acceptAlert(this: DevToolsDriver) { - if (!this.activeDialog) { - throw new Error('no such alert') - } - - await this.activeDialog.accept(this.activeDialog.defaultValue()) - delete this.activeDialog - return null -} diff --git a/packages/devtools/src/commands/addCookie.ts b/packages/devtools/src/commands/addCookie.ts deleted file mode 100644 index f222a8f5d..000000000 --- a/packages/devtools/src/commands/addCookie.ts +++ /dev/null @@ -1,32 +0,0 @@ -import type { Cookie } from '@wdio/protocols' - -import type DevToolsDriver from '../devtoolsdriver.js' - -/** - * The Add Cookie command adds a single cookie to the cookie store - * associated with the active document's address. - * - * @alias browser.addCookie - * @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 - */ -export default async function addCookie( - this: DevToolsDriver, - { cookie }: { cookie: Cookie } -) { - const page = this.getPageHandle() - - const cookieProps = Object.keys(cookie) - if (!cookieProps.includes('name') || !cookieProps.includes('value')) { - throw new Error( - 'Provided cookie object is missing either "name" or "value" property' - ) - } - - if (typeof cookie.value !== 'string') { - cookie.value = (cookie.value as any).toString() - } - - await page.setCookie(cookie) - return null -} diff --git a/packages/devtools/src/commands/back.ts b/packages/devtools/src/commands/back.ts deleted file mode 100644 index 24d525319..000000000 --- a/packages/devtools/src/commands/back.ts +++ /dev/null @@ -1,17 +0,0 @@ -import type DevToolsDriver from '../devtoolsdriver.js' - -/** - * The Back command causes the browser to traverse one step backward - * in the joint session history of the current top-level browsing context. - * This is equivalent to pressing the back button in the browser chrome or calling `window.history.back`. - * - * @alias browser.back - * @see https://w3c.github.io/webdriver/#dfn-back - */ -export default async function back (this: DevToolsDriver) { - delete this.currentFrame - const page = this.getPageHandle() - - await page.goBack() - return null -} diff --git a/packages/devtools/src/commands/closeWindow.ts b/packages/devtools/src/commands/closeWindow.ts deleted file mode 100644 index ed37a273f..000000000 --- a/packages/devtools/src/commands/closeWindow.ts +++ /dev/null @@ -1,33 +0,0 @@ -import { v4 as uuidv4 } from 'uuid' -import { sleep } from '../utils.js' -import type DevToolsDriver from '../devtoolsdriver.js' - -/** - * The Close Window command closes the current top-level browsing context. - * Once done, if there are no more top-level browsing contexts open, - * the WebDriver session itself is closed. - * - * @alias browser.closeWindow - * @see https://w3c.github.io/webdriver/#dfn-close-window - */ -export default async function closeWindow (this: DevToolsDriver) { - delete this.currentFrame - - const page = this.getPageHandle() - await page.close() - await sleep(100) - - const handles = [...this.windows.keys()] - this.currentWindowHandle = handles[handles.length - 1] - - if (!this.currentWindowHandle) { - const page = await this.browser.newPage() - const newWindowHandle = uuidv4() - this.windows.set(newWindowHandle, page) - this.currentWindowHandle = newWindowHandle - } - - const newPage = this.getPageHandle() - await newPage.bringToFront() - return this.currentWindowHandle -} diff --git a/packages/devtools/src/commands/createWindow.ts b/packages/devtools/src/commands/createWindow.ts deleted file mode 100644 index acc7ca9a7..000000000 --- a/packages/devtools/src/commands/createWindow.ts +++ /dev/null @@ -1,43 +0,0 @@ -import command from '../scripts/createWindow.js' -import type DevToolsDriver from '../devtoolsdriver.js' - -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' } -) { - type = type || DEFAULT_WINDOW_TYPE - let newPage - - if (type === 'window') { - const page = this.getPageHandle() - - await page.evaluate(command, NEW_PAGE_URL, WINDOW_FEATURES) - const newWindowTarget = await this.browser.waitForTarget( - (target) => target.url() === NEW_PAGE_URL) - - newPage = await newWindowTarget.page() - if (!newPage) { - throw new Error('Couldn\'t find page to switch to') - } - } else { - newPage = await this.browser.newPage() - } - - await newPage.bringToFront() - return { - handle: this.currentWindowHandle, - type - } -} diff --git a/packages/devtools/src/commands/deleteAllCookies.ts b/packages/devtools/src/commands/deleteAllCookies.ts deleted file mode 100644 index 6c835d581..000000000 --- a/packages/devtools/src/commands/deleteAllCookies.ts +++ /dev/null @@ -1,19 +0,0 @@ -import type DevToolsDriver from '../devtoolsdriver.js' - -/** - * The Delete All Cookies command allows deletion of all cookies - * associated with the active document's address. - * - * @alias browser.deleteAllCookies - * @see https://w3c.github.io/webdriver/#dfn-delete-all-cookies - */ -export default async function deleteAllCookies (this: DevToolsDriver) { - const page = this.getPageHandle() - const cookies = await page.cookies() - - for (const cookie of cookies) { - await page.deleteCookie(cookie) - } - - return null -} diff --git a/packages/devtools/src/commands/deleteCookie.ts b/packages/devtools/src/commands/deleteCookie.ts deleted file mode 100644 index 4524206b5..000000000 --- a/packages/devtools/src/commands/deleteCookie.ts +++ /dev/null @@ -1,24 +0,0 @@ -import type DevToolsDriver from '../devtoolsdriver.js' - -/** - * 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. - * - * @alias browser.deleteCookie - * @see https://w3c.github.io/webdriver/#dfn-delete-cookie - * @param {string} name name of the cookie to delete - */ -export default async function deleteCookie ( - this: DevToolsDriver, - { name }: { name: string } -) { - const page = this.getPageHandle() - const cookies = await page.cookies() - const cookieToDelete = cookies.find((cookie) => cookie.name === name) - - if (cookieToDelete) { - await page.deleteCookie(cookieToDelete) - } - - return null -} diff --git a/packages/devtools/src/commands/deleteSession.ts b/packages/devtools/src/commands/deleteSession.ts deleted file mode 100644 index e26d63ae2..000000000 --- a/packages/devtools/src/commands/deleteSession.ts +++ /dev/null @@ -1,14 +0,0 @@ -import type DevToolsDriver from '../devtoolsdriver.js' - -/** - * The Delete Session command closes any top-level browsing contexts associated - * with the current session, terminates the connection, and finally closes the current session. - * - * @alias browser.deleteSession - * @see https://w3c.github.io/webdriver/#dfn-delete-session - */ -export default async function deleteSession (this: DevToolsDriver) { - await this.browser.close() - this.windows.clear() - return null -} diff --git a/packages/devtools/src/commands/dismissAlert.ts b/packages/devtools/src/commands/dismissAlert.ts deleted file mode 100644 index fadfad237..000000000 --- a/packages/devtools/src/commands/dismissAlert.ts +++ /dev/null @@ -1,19 +0,0 @@ -import type DevToolsDriver from '../devtoolsdriver.js' - -/** - * 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, - * has the same effect as accepting it. - * - * @alias browser.dismissAlert - * @see https://w3c.github.io/webdriver/#dfn-dismiss-alert - */ -export default async function dismissAlert (this: DevToolsDriver) { - if (!this.activeDialog) { - throw new Error('no such alert') - } - - await this.activeDialog.dismiss() - delete this.activeDialog - return null -} diff --git a/packages/devtools/src/commands/elementClear.ts b/packages/devtools/src/commands/elementClear.ts deleted file mode 100644 index 3c3b6bd71..000000000 --- a/packages/devtools/src/commands/elementClear.ts +++ /dev/null @@ -1,25 +0,0 @@ -import command from '../scripts/elementClear.js' -import { getStaleElementError } from '../utils.js' -import type DevToolsDriver from '../devtoolsdriver.js' - -/** - * The Element Clear command scrolls into view an editable or resettable element and then attempts - * to clear its selected files or text content. - * - * @alias browser.elementClear - * @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) - */ -export default async function elementClear ( - this: DevToolsDriver, - { elementId }: { elementId: string } -) { - const elementHandle = await this.elementStore.get(elementId) - if (!elementHandle) { - throw getStaleElementError(elementId) - } - - const page = this.getPageHandle(true) - await page.$eval('html', command, elementHandle) - return null -} diff --git a/packages/devtools/src/commands/elementClick.ts b/packages/devtools/src/commands/elementClick.ts deleted file mode 100644 index 2602b0765..000000000 --- a/packages/devtools/src/commands/elementClick.ts +++ /dev/null @@ -1,56 +0,0 @@ -import getElementTagName from './getElementTagName.js' -import selectOptionScript from '../scripts/selectOption.js' -import { getStaleElementError } from '../utils.js' -import type DevToolsDriver from '../devtoolsdriver.js' - -/** - * 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, - * an element click intercepted error is returned. - * - * If the element is outside the viewport, an element not interactable error is returned. - * - * @alias browser.elementClick - * @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) - */ -export default async function elementClick ( - this: DevToolsDriver, - { elementId }: { elementId: string } -) { - const page = this.getPageHandle() - const elementHandle = await this.elementStore.get(elementId) - - if (!elementHandle) { - throw getStaleElementError(elementId) - } - - /** - * in order to allow clicking on option elements (as WebDriver does) - * we need to check if the element is such a an element and select - * it instead of actually executing the click - */ - const tagName = await getElementTagName.call(this, { elementId }) - if (tagName === 'option') { - return page.$eval('html', selectOptionScript, elementHandle) - } - - /** - * ensure to fulfill the click promise if the click has triggered an alert - */ - return new Promise((resolve, reject) => { - /** - * listen on possible modal dialogs that might pop up due to the - * click action, just continue in this case - */ - const dialogHandler = () => resolve(null) - page.once('dialog', dialogHandler) - return elementHandle.click().then(() => { - /** - * no modals popped up, so clean up the listener - */ - page.off('dialog', dialogHandler) - resolve(null) - }).catch(reject) - }) -} diff --git a/packages/devtools/src/commands/elementSendKeys.ts b/packages/devtools/src/commands/elementSendKeys.ts deleted file mode 100644 index a5c1340a1..000000000 --- a/packages/devtools/src/commands/elementSendKeys.ts +++ /dev/null @@ -1,64 +0,0 @@ -import path from 'node:path' -import { UNICODE_CHARACTERS } from '@wdio/utils' -import type { ElementHandle, KeyInput } from 'puppeteer-core' - -import { getStaleElementError } from '../utils.js' -import type DevToolsDriver from '../devtoolsdriver.js' - -/** - * 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, - * an element not interactable error is returned. The key input state used for input - * may be cleared mid-way through "typing" by sending the null key, which is U+E000 (NULL) - * - * @alias browser.elementSendKeys - * @see https://w3c.github.io/webdriver/#dfn-element-send-keys - * @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 - */ -export default async function elementSendKeys ( - this: DevToolsDriver, - { elementId, text }: { elementId: string, text: string } -) { - const elementHandle = await this.elementStore.get(elementId) as any as ElementHandle - - if (!elementHandle) { - throw getStaleElementError(elementId) - } - - await elementHandle.focus() - const page = this.getPageHandle() - const propertyHandles = { - tagName: await elementHandle.getProperty('tagName'), - type: await elementHandle.getProperty('type') - } - - const tagName = await propertyHandles.tagName?.jsonValue() as unknown as string - const type = await propertyHandles.type?.jsonValue() as unknown as string - let typeInput: string[] = [text] - - for (const [key, value] of Object.entries(UNICODE_CHARACTERS)) { - typeInput = typeInput.reduce((input, val) => [ - ...input, - ...val.split(value).flatMap( - (value: string, index: number, array: string[]) => - array.length - 1 !== index // check for the last item - ? [value, key] - : value, - ) - ], [] as string[]) - } - - if (tagName === 'INPUT' && type === 'file') { - const paths = (text || '').split('\n').map(p => path.resolve(p)) - await elementHandle.uploadFile(...paths) - } else { - for (const input of typeInput) { - UNICODE_CHARACTERS[input as keyof typeof UNICODE_CHARACTERS] - ? await page.keyboard.press(input as KeyInput) - : await page.keyboard.type(input) - } - } - - return null -} diff --git a/packages/devtools/src/commands/executeAsyncScript.ts b/packages/devtools/src/commands/executeAsyncScript.ts deleted file mode 100644 index c4c3d3d4f..000000000 --- a/packages/devtools/src/commands/executeAsyncScript.ts +++ /dev/null @@ -1,45 +0,0 @@ -import command from '../scripts/executeAsyncScript.js' -import { transformExecuteArgs, transformExecuteResult } from '../utils.js' -import { SERIALIZE_PROPERTY, SERIALIZE_FLAG } from '../constants.js' -import type DevToolsDriver from '../devtoolsdriver.js' - -/** - * 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. - * Instead an additional argument is provided as the final argument to the function. - * This is a function that, when called, returns its first argument as the response. - * - * @alias browser.executeScript - * @see https://w3c.github.io/webdriver/#dfn-execute-async-script - * @param {string} script a string, the Javascript function body you want executed - * @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. - */ -export default async function executeAsyncScript ( - this: DevToolsDriver, - { script, args }: { script: string, args: any[] } -) { - const page = this.getPageHandle(true) - const scriptTimeout = this.timeouts.get('script') || 0 - script = script.trim() - - if (script.startsWith('return (')) { - script = script.slice(7) - } - - if (script.startsWith('return')) { - script = `(function () { ${script} }).apply(null, arguments)` - } - - const result = await page.$eval( - 'html', - command, - script, - scriptTimeout, - SERIALIZE_PROPERTY, - SERIALIZE_FLAG, - ...(await transformExecuteArgs.call(this, args)) - ) - - return transformExecuteResult.call(this, page, result) -} diff --git a/packages/devtools/src/commands/executeScript.ts b/packages/devtools/src/commands/executeScript.ts deleted file mode 100644 index 6386f0109..000000000 --- a/packages/devtools/src/commands/executeScript.ts +++ /dev/null @@ -1,59 +0,0 @@ -import command from '../scripts/executeScript.js' -import { transformExecuteArgs, transformExecuteResult } from '../utils.js' -import { SERIALIZE_PROPERTY, SERIALIZE_FLAG } from '../constants.js' -import type DevToolsDriver from '../devtoolsdriver.js' - -/** - * The Execute Script command executes a JavaScript function in the context of the - * current browsing context and returns the return value of the function. - * - * @alias browser.executeScript - * @see https://w3c.github.io/webdriver/#dfn-execute-script - * @param {string} script a string, the Javascript function body you want executed - * @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. - */ -export default async function executeScript ( - this: DevToolsDriver, - { script, args }: { script: string, args: any[] } -) { - const page = this.getPageHandle(true) - const scriptTimeout = this.timeouts.get('script') - script = script.trim() - - if (script.startsWith('return (')) { - script = script.slice(7) - } - - if (script.startsWith('return')) { - script = `(function () { ${script} }).apply(null, arguments)` - } - - const executePromise = page.$eval( - 'html', - command, - script, - SERIALIZE_PROPERTY, - SERIALIZE_FLAG, - ...(await transformExecuteArgs.call(this, args)) - ) - - let executeTimeout: undefined | ReturnType - const timeoutPromise = new Promise((_, reject) => { - executeTimeout = setTimeout(() => { - const timeoutError = `script timeout${ - this.activeDialog - ? ' reason: a browser dialog has opened as result of a executeScript call' - : '' - }` - - return reject(new Error(timeoutError)) - }, scriptTimeout) - }) - - const result = await Promise.race([executePromise, timeoutPromise]).finally(() => { - clearTimeout(executeTimeout) - }) - - return transformExecuteResult.call(this, page, result) -} diff --git a/packages/devtools/src/commands/findElement.ts b/packages/devtools/src/commands/findElement.ts deleted file mode 100644 index f20cadc0c..000000000 --- a/packages/devtools/src/commands/findElement.ts +++ /dev/null @@ -1,40 +0,0 @@ -import { SUPPORTED_SELECTOR_STRATEGIES } from '../constants.js' -import { findElement as findElementUtil } from '../utils.js' -import type DevToolsDriver from '../devtoolsdriver.js' - -/** - * The Find Element command is used to find an element in the current browsing context - * that can be used for future commands. - * - * @alias browser.findElement - * @see https://w3c.github.io/webdriver/#dfn-find-element - * @param {string} using a valid element location strategy - * @param {string} value the actual selector that will be used to find an element - * @return {Object} A JSON representation of an element object. - */ -export default function findElement ( - this: DevToolsDriver, - { using, value }: { using: string, value: string } -) { - if (!SUPPORTED_SELECTOR_STRATEGIES.includes(using)) { - throw new Error(`selector strategy "${using}" is not yet supported`) - } - - if (using === 'link text') { - using = 'xpath' - value = `//a[normalize-space() = "${value}"]` - } else if (using === 'partial link text') { - using = 'xpath' - value = `//a[contains(., "${value}")]` - } else if (using === 'shadow') { - /** - * `shadow/` is the way query-selector-shadow-dom - * understands to query for shadow elements - */ - using = 'css' - value = `shadow/${value}` - } - - const page = this.getPageHandle(true) - return findElementUtil.call(this, page, using, value) -} diff --git a/packages/devtools/src/commands/findElementFromElement.ts b/packages/devtools/src/commands/findElementFromElement.ts deleted file mode 100644 index 64bf3b9df..000000000 --- a/packages/devtools/src/commands/findElementFromElement.ts +++ /dev/null @@ -1,45 +0,0 @@ -import { SUPPORTED_SELECTOR_STRATEGIES } from '../constants.js' -import { findElement, getStaleElementError } from '../utils.js' -import type DevToolsDriver from '../devtoolsdriver.js' - -/** - * 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. - * - * @alias browser.findElementFromElement - * @see https://w3c.github.io/webdriver/#dfn-find-element-from-element - * @param {string} using a valid element location strategy - * @param {string} value the actual selector that will be used to find an element - * @return {Object} A JSON representation of an element object. - */ -export default async function findElementFromElement ( - this: DevToolsDriver, - { elementId, using, value }: { elementId: string, using: string, value: string } -) { - if (!SUPPORTED_SELECTOR_STRATEGIES.includes(using)) { - throw new Error(`selector strategy "${using}" is not yet supported`) - } - - const elementHandle = await this.elementStore.get(elementId) - - if (!elementHandle) { - throw getStaleElementError(elementId) - } - - if (using === 'link text') { - using = 'xpath' - value = `.//a[normalize-space() = "${value}"]` - } else if (using === 'partial link text') { - using = 'xpath' - value = `.//a[contains(., "${value}")]` - } else if (using === 'shadow') { - /** - * `shadow/` is the way query-selector-shadow-dom - * understands to query for shadow elements - */ - using = 'css' - value = `shadow/${value}` - } - - return findElement.call(this, elementHandle, using, value) -} diff --git a/packages/devtools/src/commands/findElementFromShadowRoot.ts b/packages/devtools/src/commands/findElementFromShadowRoot.ts deleted file mode 100644 index 45d5253a9..000000000 --- a/packages/devtools/src/commands/findElementFromShadowRoot.ts +++ /dev/null @@ -1,29 +0,0 @@ -import findElementFromElement from './findElementFromElement.js' -import type DevToolsDriver from '../devtoolsdriver.js' - -/** - * The Find Element From Shadow Root command is used to find an element - * within the shadow root of an element that can be used for future commands. - * This command returns JSON representation of the element that can be passed - * to $ command to transform the reference to an extended WebdriverIO element. - * - * @alias browser.findElementFromShadowRoot - * @see https://w3c.github.io/webdriver/#find-element-from-shadow-root - * @param {string} using a valid element location strategy - * @param {string} value the actual selector that will be used to find an element - * @return {Object} A JSON representation of an element shadow object, e.g. `{ 'element-6066-11e4-a52e-4f735466cecf': 'ELEMENT_1' }`. - */ -export default async function findElementFromShadowRoot ( - this: DevToolsDriver, - { shadowId, using, value }: { shadowId: string, using: string, value: string } -) { - if (using !== 'css selector') { - throw new Error('Fetching elements from a shadow element using something other than "css selector" is currently not supported.') - } - - return findElementFromElement.call(this, { - elementId: shadowId, - using: 'shadow', - value - }) -} diff --git a/packages/devtools/src/commands/findElements.ts b/packages/devtools/src/commands/findElements.ts deleted file mode 100644 index 527555db2..000000000 --- a/packages/devtools/src/commands/findElements.ts +++ /dev/null @@ -1,40 +0,0 @@ -import { SUPPORTED_SELECTOR_STRATEGIES } from '../constants.js' -import { findElements as findElementsUtil } from '../utils.js' -import type DevToolsDriver from '../devtoolsdriver.js' - -/** - * The Find Elements command is used to find elements - * in the current browsing context that can be used for future commands. - * - * @alias browser.findElements - * @see https://w3c.github.io/webdriver/#dfn-find-elements - * @param {string} using a valid element location strategy - * @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. - */ -export default async function findElements ( - this: DevToolsDriver, - { using, value }: { using: string, value: string } -) { - if (!SUPPORTED_SELECTOR_STRATEGIES.includes(using)) { - throw new Error(`selector strategy "${using}" is not yet supported`) - } - - if (using === 'link text') { - using = 'xpath' - value = `//a[normalize-space() = "${value}"]` - } else if (using === 'partial link text') { - using = 'xpath' - value = `//a[contains(., "${value}")]` - } else if (using === 'shadow') { - /** - * `shadow/` is the way query-selector-shadow-dom - * understands to query for shadow elements - */ - using = 'css' - value = `shadow/${value}` - } - - const page = this.getPageHandle(true) - return findElementsUtil.call(this, page, using, value) -} diff --git a/packages/devtools/src/commands/findElementsFromElement.ts b/packages/devtools/src/commands/findElementsFromElement.ts deleted file mode 100644 index d03dc7301..000000000 --- a/packages/devtools/src/commands/findElementsFromElement.ts +++ /dev/null @@ -1,45 +0,0 @@ -import { SUPPORTED_SELECTOR_STRATEGIES } from '../constants.js' -import { findElements, getStaleElementError } from '../utils.js' -import type DevToolsDriver from '../devtoolsdriver.js' - -/** - * 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. - * - * @alias browser.findElementFromElements - * @see https://w3c.github.io/webdriver/#dfn-find-elements-from-element - * @param {string} using a valid element location strategy - * @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. - */ -export default async function findElementFromElements ( - this: DevToolsDriver, - { elementId, using, value }: { elementId: string, using: string, value: string } -) { - if (!SUPPORTED_SELECTOR_STRATEGIES.includes(using)) { - throw new Error(`selector strategy "${using}" is not yet supported`) - } - - const elementHandle = await this.elementStore.get(elementId) - - if (!elementHandle) { - throw getStaleElementError(elementId) - } - - if (using === 'link text') { - using = 'xpath' - value = `.//a[normalize-space() = "${value}"]` - } else if (using === 'partial link text') { - using = 'xpath' - value = `.//a[contains(., "${value}")]` - } else if (using === 'shadow') { - /** - * `shadow/` is the way query-selector-shadow-dom - * understands to query for shadow elements - */ - using = 'css' - value = `shadow/${value}` - } - - return findElements.call(this, elementHandle, using, value) -} diff --git a/packages/devtools/src/commands/findElementsFromShadowRoot.ts b/packages/devtools/src/commands/findElementsFromShadowRoot.ts deleted file mode 100644 index fc3816a89..000000000 --- a/packages/devtools/src/commands/findElementsFromShadowRoot.ts +++ /dev/null @@ -1,29 +0,0 @@ -import findElementsFromElement from './findElementsFromElement.js' -import type DevToolsDriver from '../devtoolsdriver.js' - -/** - * The Find Elements command is used to find elements within the shadow root of an - * element that can be used for future commands. This command returns array of JSON - * representation of the elements that can be passed to $ command to transform the - * reference to an extended WebdriverIO element (See findElement). - * - * @alias browser.findElementsFromShadowRoot - * @see https://w3c.github.io/webdriver/#find-elements-from-shadow-root - * @param {string} using a valid element location strategy - * @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, e.g. `{ 'element-6066-11e4-a52e-4f735466cecf': 'ELEMENT_1' }`. - */ -export default async function findElementsFromShadowRoot ( - this: DevToolsDriver, - { shadowId, using, value }: { shadowId: string, using: string, value: string } -) { - if (using !== 'css selector') { - throw new Error('Fetching elements from a shadow element using something other than "css selector" is currently not supported.') - } - - return findElementsFromElement.call(this, { - elementId: shadowId, - using: 'shadow', - value - }) -} diff --git a/packages/devtools/src/commands/forward.ts b/packages/devtools/src/commands/forward.ts deleted file mode 100644 index a86f8d40d..000000000 --- a/packages/devtools/src/commands/forward.ts +++ /dev/null @@ -1,15 +0,0 @@ -import type DevToolsDriver from '../devtoolsdriver.js' - -/** - * The Forward command causes the browser to traverse one step forwards - * in the joint session history of the current top-level browsing context. - * - * @alias browser.forward - * @see https://w3c.github.io/webdriver/#dfn-forward - */ -export default async function forward (this: DevToolsDriver) { - delete this.currentFrame - const page = this.getPageHandle() - await page.goForward() - return null -} diff --git a/packages/devtools/src/commands/getActiveElement.ts b/packages/devtools/src/commands/getActiveElement.ts deleted file mode 100644 index bcf3085cd..000000000 --- a/packages/devtools/src/commands/getActiveElement.ts +++ /dev/null @@ -1,43 +0,0 @@ -import findElement from './findElement.js' -import command from '../scripts/getActiveElement.js' -import cleanUp from '../scripts/cleanUpSerializationSelector.js' -import { SERIALIZE_PROPERTY } from '../constants.js' -import type DevToolsDriver from '../devtoolsdriver.js' - -/** - * Get Active Element returns the active element of the current browsing context’s document element. - * - * @param browser.getActiveElement - * @see https://w3c.github.io/webdriver/#dfn-get-active-element - * @return {Object} A JSON representation of an element object. - */ -export default async function getActiveElement ( - this: DevToolsDriver -) { - const page = this.getPageHandle(true) - const selector = `[${SERIALIZE_PROPERTY}]` - - /** - * set data property to active element to allow to query for it - */ - const hasElem = await page.$eval('html', command, SERIALIZE_PROPERTY) - - if (!hasElem) { - throw new Error('no element active') - } - - /** - * query for element - */ - const activeElement = await findElement.call(this, { - using: 'css selector', - value: selector - }) - - /** - * clean up data property - */ - await page.$eval(selector, cleanUp, SERIALIZE_PROPERTY) - - return activeElement -} diff --git a/packages/devtools/src/commands/getAlertText.ts b/packages/devtools/src/commands/getAlertText.ts deleted file mode 100644 index 680cbc5f6..000000000 --- a/packages/devtools/src/commands/getAlertText.ts +++ /dev/null @@ -1,17 +0,0 @@ -import type DevToolsDriver from '../devtoolsdriver.js' - -/** - * The Get Alert Text command returns the message of the current user prompt. - * If there is no current user prompt, it returns an error. - * - * @alias browser.getAlertText - * @see https://w3c.github.io/webdriver/#dfn-get-alert-text - * @return {string} The message of the user prompt. - */ -export default function getAlertText (this: DevToolsDriver) { - if (!this.activeDialog) { - throw new Error('no such alert') - } - - return this.activeDialog.message() -} diff --git a/packages/devtools/src/commands/getAllCookies.ts b/packages/devtools/src/commands/getAllCookies.ts deleted file mode 100644 index d393e6841..000000000 --- a/packages/devtools/src/commands/getAllCookies.ts +++ /dev/null @@ -1,14 +0,0 @@ -import type DevToolsDriver from '../devtoolsdriver.js' - -/** - * The Get All Cookies command returns all cookies associated with the address - * of the current browsing context’s active document. - * - * @alias browser.getAllCookies - * @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`. - */ -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 deleted file mode 100644 index 0298df0ea..000000000 --- a/packages/devtools/src/commands/getElementAttribute.ts +++ /dev/null @@ -1,25 +0,0 @@ -import command from '../scripts/getElementAttribute.js' -import { getStaleElementError } from '../utils.js' -import type DevToolsDriver from '../devtoolsdriver.js' - -/** - * The Get Element Attribute command will return the attribute of a web element. - * - * @alias browser.getElementAttribute - * @see https://w3c.github.io/webdriver/#dfn-get-element-attribute - * @param {string} elementId the id of an element returned in a previous call to Find Element(s) - * @param {string} name name of the attribute value to retrieve - * @return {string} The named attribute of the element. - */ -export default async function getElementAttribute ( - this: DevToolsDriver, - { elementId, name }: { elementId: string, name: string } -) { - const elementHandle = await this.elementStore.get(elementId) - if (!elementHandle) { - throw getStaleElementError(elementId) - } - - const page = this.getPageHandle(true) - return page.$eval('html', command, elementHandle, name) -} diff --git a/packages/devtools/src/commands/getElementCSSValue.ts b/packages/devtools/src/commands/getElementCSSValue.ts deleted file mode 100644 index c3116326a..000000000 --- a/packages/devtools/src/commands/getElementCSSValue.ts +++ /dev/null @@ -1,26 +0,0 @@ -import command from '../scripts/getElementCSSValue.js' -import { getStaleElementError } from '../utils.js' -import type DevToolsDriver from '../devtoolsdriver.js' - -/** - * The Get Element CSS Value command retrieves the computed value - * of the given CSS property of the given web element. - * - * @alias browser.getElementCSSValue - * @see https://w3c.github.io/webdriver/#dfn-get-element-css-value - * @param {string} elementId the id of an element returned in a previous call to Find Element(s) - * @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). - */ -export default async function getElementCSSValue ( - this: DevToolsDriver, - { elementId, propertyName }: { elementId: string, propertyName: string } -) { - const elementHandle = await this.elementStore.get(elementId) - if (!elementHandle) { - throw getStaleElementError(elementId) - } - - const page = this.getPageHandle(true) - return page.$eval('html', command, elementHandle, propertyName) -} diff --git a/packages/devtools/src/commands/getElementComputedLabel.ts b/packages/devtools/src/commands/getElementComputedLabel.ts deleted file mode 100644 index 0dcf2156c..000000000 --- a/packages/devtools/src/commands/getElementComputedLabel.ts +++ /dev/null @@ -1,26 +0,0 @@ -import type DevToolsDriver from '../devtoolsdriver.js' - -/** - * Get the computed WAI-ARIA label of an element. - * - * @alias browser.getElementComputedLabel - * @see https://w3c.github.io/webdriver/#get-computed-label - * @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. - */ -export default async function getElementComputedLabel ( - this: DevToolsDriver, - { elementId }: { elementId: string } -) { - const page = this.getPageHandle(true) - const elementHandle = await this.elementStore.get(elementId) - const snapshot = await page.accessibility.snapshot({ - root: elementHandle - }) - - if (!snapshot) { - return '' - } - - return snapshot.name -} diff --git a/packages/devtools/src/commands/getElementComputedRole.ts b/packages/devtools/src/commands/getElementComputedRole.ts deleted file mode 100644 index d6bca2e1f..000000000 --- a/packages/devtools/src/commands/getElementComputedRole.ts +++ /dev/null @@ -1,26 +0,0 @@ -import type DevToolsDriver from '../devtoolsdriver.js' - -/** - * Get the computed WAI-ARIA role of an element. - * - * @alias browser.getElementComputedRole - * @see https://w3c.github.io/webdriver/#get-computed-role - * @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. - */ -export default async function getElementComputedRole ( - this: DevToolsDriver, - { elementId }: { elementId: string } -) { - const page = this.getPageHandle(true) - const elementHandle = await this.elementStore.get(elementId) - const snapshot = await page.accessibility.snapshot({ - root: elementHandle - }) - - if (!snapshot) { - return 'Ignored' - } - - return snapshot.role -} diff --git a/packages/devtools/src/commands/getElementProperty.ts b/packages/devtools/src/commands/getElementProperty.ts deleted file mode 100644 index f964f6c18..000000000 --- a/packages/devtools/src/commands/getElementProperty.ts +++ /dev/null @@ -1,29 +0,0 @@ -import { getStaleElementError } from '../utils.js' -import type DevToolsDriver from '../devtoolsdriver.js' - -/** - * The Get Element Property command will return the result of getting a property of an element. - * - * @alias browser.getElementProperty - * @see https://w3c.github.io/webdriver/#dfn-get-element-property - * @param {string} elementId the id of an element returned in a previous call to Find Element(s) - * @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. - */ -export default async function getElementProperty ( - this: DevToolsDriver, - { elementId, name }: { elementId: string, name: string } -) { - const elementHandle = await this.elementStore.get(elementId) - - if (!elementHandle) { - throw getStaleElementError(elementId) - } - - const jsHandle = await elementHandle.getProperty(name as any) - if (!jsHandle) { - return null - } - - return jsHandle.jsonValue() -} diff --git a/packages/devtools/src/commands/getElementRect.ts b/packages/devtools/src/commands/getElementRect.ts deleted file mode 100644 index bd2cfc678..000000000 --- a/packages/devtools/src/commands/getElementRect.ts +++ /dev/null @@ -1,24 +0,0 @@ -import command from '../scripts/getElementRect.js' -import { getStaleElementError } from '../utils.js' -import type DevToolsDriver from '../devtoolsdriver.js' - -/** - * The Get Element Rect command returns the dimensions and coordinates of the given web element. - * - * @alias browser.getElementRect - * @see https://w3c.github.io/webdriver/#dfn-get-element-rect - * @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. - */ -export default async function getElementRect ( - this: DevToolsDriver, - { elementId }: { elementId: string } -) { - const elementHandle = await this.elementStore.get(elementId) - if (!elementHandle) { - throw getStaleElementError(elementId) - } - - const page = this.getPageHandle(true) - return page.$eval('html', command, elementHandle) -} diff --git a/packages/devtools/src/commands/getElementShadowRoot.ts b/packages/devtools/src/commands/getElementShadowRoot.ts deleted file mode 100644 index 511b70f47..000000000 --- a/packages/devtools/src/commands/getElementShadowRoot.ts +++ /dev/null @@ -1,29 +0,0 @@ -import { getStaleElementError } from '../utils.js' -import { SHADOW_ELEMENT_KEY } from '../constants.js' -import type DevToolsDriver from '../devtoolsdriver.js' - -/** - * Get the shadow root object of an element. The result object can be used to fetch - * elements within this shadow root using e.g. `findElementFromShadowRoots` or `findElementsFromShadowRoots`. - * - * @alias browser.getElementShadowRoot - * @see https://w3c.github.io/webdriver/#dfn-get-element-rect - * @param {string} elementId the id of an element returned in a previous call to Find Element(s) - * @return {string} A JSON representation of an element shadow root, e.g. `{ 'shadow-6066-11e4-a52e-4f735466cecf': 'ELEMENT_1' }`. - */ -export default async function getElementShadowRoot ( - this: DevToolsDriver, - { elementId }: { elementId: string } -) { - const elementHandle = await this.elementStore.get(elementId) - if (!elementHandle) { - throw getStaleElementError(elementId) - } - - /** - * given that puppeteer doesn't allow to get an element handle from a shadow root - * we just return the given element and use the shadow selector in `findElementFromShadowRoots` - * and `findElementsFromShadowRoots` commands - */ - return { [SHADOW_ELEMENT_KEY]: elementId } -} diff --git a/packages/devtools/src/commands/getElementTagName.ts b/packages/devtools/src/commands/getElementTagName.ts deleted file mode 100644 index 50babf9d4..000000000 --- a/packages/devtools/src/commands/getElementTagName.ts +++ /dev/null @@ -1,25 +0,0 @@ -import command from '../scripts/getElementTagName.js' -import { getStaleElementError } from '../utils.js' -import type DevToolsDriver from '../devtoolsdriver.js' - -/** - * The Get Element Tag Name command returns the qualified element name of the given web element. - * - * @alias browser.getElementTagName - * @see https://w3c.github.io/webdriver/#dfn-get-element-tag-name - * @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. - */ -export default async function getElementTagName ( - this: DevToolsDriver, - { elementId }: { elementId: string } -) { - const elementHandle = await this.elementStore.get(elementId) - if (!elementHandle) { - throw getStaleElementError(elementId) - } - - const page = this.getPageHandle(true) - const result = await page.$eval('html', command, elementHandle) - return (result || '').toLowerCase() -} diff --git a/packages/devtools/src/commands/getElementText.ts b/packages/devtools/src/commands/getElementText.ts deleted file mode 100644 index 3eb7286d4..000000000 --- a/packages/devtools/src/commands/getElementText.ts +++ /dev/null @@ -1,26 +0,0 @@ -import command from '../scripts/getElementText.js' -import { getStaleElementError } from '../utils.js' -import type DevToolsDriver from '../devtoolsdriver.js' - -/** - * 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 - * by their link text and partial link text. - * - * @alias browser.getElementText - * @see https://w3c.github.io/webdriver/#dfn-get-element-text - * @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). - */ -export default async function getElementText ( - this: DevToolsDriver, - { elementId }: { elementId: string } -) { - const elementHandle = await this.elementStore.get(elementId) - if (!elementHandle) { - throw getStaleElementError(elementId) - } - - const page = this.getPageHandle(true) - return page.$eval('html', command, elementHandle) -} diff --git a/packages/devtools/src/commands/getNamedCookie.ts b/packages/devtools/src/commands/getNamedCookie.ts deleted file mode 100644 index d48d972c6..000000000 --- a/packages/devtools/src/commands/getNamedCookie.ts +++ /dev/null @@ -1,26 +0,0 @@ -import type DevToolsDriver from '../devtoolsdriver.js' - -/** - * 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. - * If no cookie is found, a no such cookie error is returned. - * - * @alias browser.getNamedCookie - * @see https://w3c.github.io/webdriver/#dfn-get-named-cookie - * @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. - */ -export default async function getNamedCookie ( - this: DevToolsDriver, - { name }: { name: string } -) { - const page = this.getPageHandle() - const cookies = await page.cookies() - const cookie = cookies.find((cookie) => cookie.name === name) - - if (!cookie) { - throw new Error(`No cookie with name ${name}`) - } - - return cookie -} diff --git a/packages/devtools/src/commands/getPageSource.ts b/packages/devtools/src/commands/getPageSource.ts deleted file mode 100644 index 83541738b..000000000 --- a/packages/devtools/src/commands/getPageSource.ts +++ /dev/null @@ -1,14 +0,0 @@ -import type DevToolsDriver from '../devtoolsdriver.js' - -/** - * The Get Page Source command returns a string serialization of the DOM - * of the current browsing context active document. - * - * @see https://w3c.github.io/webdriver/#dfn-get-page-source - * @alias browser.getPageSource - * @return {string} the DOM of the current browsing context active document - */ -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 deleted file mode 100644 index 9a91f8433..000000000 --- a/packages/devtools/src/commands/getTimeouts.ts +++ /dev/null @@ -1,16 +0,0 @@ -import type DevToolsDriver from '../devtoolsdriver.js' - -/** - * The Get Timeouts command gets timeout durations associated with the current session. - * - * @alias browser.getTimeouts - * @see https://w3c.github.io/webdriver/#dfn-get-timeouts - * @return {Object} Object containing timeout durations for `script`, `pageLoad` and `implicit` timeouts. - */ -export default function getTimeouts (this: DevToolsDriver) { - return { - implicit: this.timeouts.get('implicit'), - pageLoad: this.timeouts.get('pageLoad'), - script: this.timeouts.get('script') - } -} diff --git a/packages/devtools/src/commands/getTitle.ts b/packages/devtools/src/commands/getTitle.ts deleted file mode 100644 index 4d9f28516..000000000 --- a/packages/devtools/src/commands/getTitle.ts +++ /dev/null @@ -1,14 +0,0 @@ -import type DevToolsDriver from '../devtoolsdriver.js' - -/** - * The Get Title command returns the document title of the current top-level - * browsing context, equivalent to calling `document.title`. - * - * @alias browser.getTitle - * @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. - */ -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 deleted file mode 100644 index 38b4f7b46..000000000 --- a/packages/devtools/src/commands/getUrl.ts +++ /dev/null @@ -1,14 +0,0 @@ -import command from '../scripts/getUrl.js' -import type DevToolsDriver from '../devtoolsdriver.js' - -/** - * The Get Current URL command returns the URL of the current top-level browsing context - * - * @alias browser.getUrl - * @see https://w3c.github.io/webdriver/#dfn-get-current-url - * @returns {String} current document URL of the top-level browsing context. - */ -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 deleted file mode 100644 index 01023ae03..000000000 --- a/packages/devtools/src/commands/getWindowHandle.ts +++ /dev/null @@ -1,13 +0,0 @@ -import type DevToolsDriver from '../devtoolsdriver.js' - -/** - * 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. - * - * @alias browser.getWindowHandle - * @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. - */ -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 deleted file mode 100644 index a17b59a72..000000000 --- a/packages/devtools/src/commands/getWindowHandles.ts +++ /dev/null @@ -1,14 +0,0 @@ -import type DevToolsDriver from '../devtoolsdriver.js' - -/** - * The Get Window Handles command returns a list of window handles - * for every open top-level browsing context. - * The order in which the window handles are returned is arbitrary. - * - * @alias browser.getWindowHandles - * @see https://w3c.github.io/webdriver/#dfn-get-window-handles - * @return {string[]} An array which is a list of window handles. - */ -export default async function getWindowHandles(this: DevToolsDriver) { - return Array.from(this.windows.keys()) -} diff --git a/packages/devtools/src/commands/getWindowRect.ts b/packages/devtools/src/commands/getWindowRect.ts deleted file mode 100644 index 542f8e61f..000000000 --- a/packages/devtools/src/commands/getWindowRect.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { DEFAULT_WIDTH, DEFAULT_HEIGHT } from '../constants.js' -import type DevToolsDriver from '../devtoolsdriver.js' - -/** - * 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. - * - * @alias browser.getWindowRect - * @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`. - */ -export default async function getWindowRect (this: DevToolsDriver) { - const page = this.getPageHandle() - const viewport = await page.viewport() || {} - return Object.assign( - { width: DEFAULT_WIDTH, height: DEFAULT_HEIGHT, x: 0, y: 0 }, - viewport - ) -} diff --git a/packages/devtools/src/commands/index.ts b/packages/devtools/src/commands/index.ts deleted file mode 100644 index dd63b1f17..000000000 --- a/packages/devtools/src/commands/index.ts +++ /dev/null @@ -1,69 +0,0 @@ -import acceptAlert from './acceptAlert.js' -import addCookie from './addCookie.js' -import back from './back.js' -import closeWindow from './closeWindow.js' -import createWindow from './createWindow.js' -import deleteAllCookies from './deleteAllCookies.js' -import deleteCookie from './deleteCookie.js' -import deleteSession from './deleteSession.js' -import dismissAlert from './dismissAlert.js' -import elementClear from './elementClear.js' -import elementClick from './elementClick.js' -import elementSendKeys from './elementSendKeys.js' -import executeAsyncScript from './executeAsyncScript.js' -import executeScript from './executeScript.js' -import findElementFromElement from './findElementFromElement.js' -import findElementFromShadowRoot from './findElementFromShadowRoot.js' -import findElementsFromElement from './findElementsFromElement.js' -import findElementsFromShadowRoot from './findElementsFromShadowRoot.js' -import findElements from './findElements.js' -import findElement from './findElement.js' -import forward from './forward.js' -import getActiveElement from './getActiveElement.js' -import getAlertText from './getAlertText.js' -import getAllCookies from './getAllCookies.js' -import getElementAttribute from './getElementAttribute.js' -import getElementComputedLabel from './getElementComputedLabel.js' -import getElementComputedRole from './getElementComputedRole.js' -import getElementCSSValue from './getElementCSSValue.js' -import getElementProperty from './getElementProperty.js' -import getElementRect from './getElementRect.js' -import getElementShadowRoot from './getElementShadowRoot.js' -import getElementTagName from './getElementTagName.js' -import getElementText from './getElementText.js' -import getNamedCookie from './getNamedCookie.js' -import getPageSource from './getPageSource.js' -import getTimeouts from './getTimeouts.js' -import getTitle from './getTitle.js' -import getUrl from './getUrl.js' -import getWindowHandles from './getWindowHandles.js' -import getWindowHandle from './getWindowHandle.js' -import getWindowRect from './getWindowRect.js' -import isElementEnabled from './isElementEnabled.js' -import isElementSelected from './isElementSelected.js' -import navigateTo from './navigateTo.js' -import newSession from './newSession.js' -import performActions from './performActions.js' -import refresh from './refresh.js' -import releaseActions from './releaseActions.js' -import sendAlertText from './sendAlertText.js' -import setTimeouts from './setTimeouts.js' -import setWindowRect from './setWindowRect.js' -import status from './status.js' -import switchToFrame from './switchToFrame.js' -import switchToParentFrame from './switchToParentFrame.js' -import switchToWindow from './switchToWindow.js' -import takeElementScreenshot from './takeElementScreenshot.js' -import takeScreenshot from './takeScreenshot.js' - -export { - acceptAlert, addCookie, back, closeWindow, createWindow, deleteAllCookies, deleteCookie, deleteSession, - dismissAlert, elementClear, elementClick, elementSendKeys, executeAsyncScript, executeScript, - findElementFromElement, findElementFromShadowRoot, findElementsFromElement, findElementsFromShadowRoot, - findElements, findElement, forward, getActiveElement, getAlertText, getAllCookies, getElementAttribute, - getElementComputedLabel, getElementComputedRole, getElementCSSValue, getElementProperty, getElementRect, - getElementShadowRoot, getElementTagName, getElementText, getNamedCookie, getPageSource, getTimeouts, - getTitle, getUrl, getWindowHandles, getWindowHandle, getWindowRect, isElementEnabled, isElementSelected, - navigateTo, newSession, performActions, refresh, releaseActions, sendAlertText, setTimeouts, setWindowRect, - status, switchToFrame, switchToParentFrame, switchToWindow, takeElementScreenshot, takeScreenshot -} \ No newline at end of file diff --git a/packages/devtools/src/commands/isElementEnabled.ts b/packages/devtools/src/commands/isElementEnabled.ts deleted file mode 100644 index 3f5f4d96f..000000000 --- a/packages/devtools/src/commands/isElementEnabled.ts +++ /dev/null @@ -1,19 +0,0 @@ -import getElementAttribute from './getElementAttribute.js' -import type DevToolsDriver from '../devtoolsdriver.js' - -/** - * Is Element Enabled determines if the referenced element is enabled or not. - * This operation only makes sense on form controls. - * - * @alias browser.isElementEnabled - * @see https://w3c.github.io/webdriver/#dfn-is-element-enabled - * @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`. - */ -export default async function isElementEnabled ( - this: DevToolsDriver, - { elementId }: { elementId: string } -) { - const result = await getElementAttribute.call(this, { elementId, name: 'disabled' }) - return result === null -} diff --git a/packages/devtools/src/commands/isElementSelected.ts b/packages/devtools/src/commands/isElementSelected.ts deleted file mode 100644 index 9659c7afa..000000000 --- a/packages/devtools/src/commands/isElementSelected.ts +++ /dev/null @@ -1,23 +0,0 @@ -import getElementProperty from './getElementProperty.js' -import getElementTagName from './getElementTagName.js' -import type DevToolsDriver from '../devtoolsdriver.js' - -/** - * 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, - * or option elements. - * - * @param browser.isElementSelected - * @see https://w3c.github.io/webdriver/#dfn-is-element-selected - * @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. - */ -export default async function isElementSelected ( - this: DevToolsDriver, - { elementId }: { elementId: string } -) { - const tagName = await getElementTagName.call(this, { elementId }) - const name = tagName === 'option' ? 'selected' : 'checked' - const isSelected = await getElementProperty.call(this, { elementId, name }) - return Boolean(isSelected) -} diff --git a/packages/devtools/src/commands/navigateTo.ts b/packages/devtools/src/commands/navigateTo.ts deleted file mode 100644 index b1a331890..000000000 --- a/packages/devtools/src/commands/navigateTo.ts +++ /dev/null @@ -1,24 +0,0 @@ -import type DevToolsDriver from '../devtoolsdriver.js' - -/** - * The navigateTo (go) command is used to cause the user agent to navigate the current - * top-level browsing context a new location. - * - * @alias browser.navigateTo - * @see https://w3c.github.io/webdriver/#dfn-navigate-to - * @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. - */ -export default async function navigateTo ( - this: DevToolsDriver, - { url }: { url: string } -) { - /** - * when navigating to a new url get out of frame scope - */ - delete this.currentFrame - - const page = this.getPageHandle() - await page.goto(url) - return null -} diff --git a/packages/devtools/src/commands/newSession.ts b/packages/devtools/src/commands/newSession.ts deleted file mode 100644 index d4f4b9bde..000000000 --- a/packages/devtools/src/commands/newSession.ts +++ /dev/null @@ -1,36 +0,0 @@ -import os from 'node:os' -import { v4 as uuidv4 } from 'uuid' - -import launch from '../launcher.js' -import { sessionMap } from '../index.js' -import type DevToolsDriver from '../devtoolsdriver.js' - -/** - * The New Session command creates a new WebDriver session with the endpoint node. - * If the creation fails, a session not created error is returned. - * - * @alias browser.newSession - * @see https://w3c.github.io/webdriver/#dfn-new-sessions - * @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. - */ -export default async function newSession ( - this: DevToolsDriver, - { capabilities }: { capabilities: WebdriverIO.Capabilities } -) { - const browser = await launch(capabilities) - const sessionId = uuidv4() - const [browserName, browserVersion] = (await browser.version()).split('/') - - sessionMap.set(sessionId, browser) - - return { - sessionId, - capabilities: { - browserName, - browserVersion, - platformName: os.platform(), - platformVersion: os.release() - } - } -} diff --git a/packages/devtools/src/commands/performActions.ts b/packages/devtools/src/commands/performActions.ts deleted file mode 100644 index cf7499229..000000000 --- a/packages/devtools/src/commands/performActions.ts +++ /dev/null @@ -1,221 +0,0 @@ -import type { KeyInput } from 'puppeteer-core/lib/esm/puppeteer/common/USKeyboardLayout.js' -import { _keyDefinitions } from 'puppeteer-core/lib/esm/puppeteer/common/USKeyboardLayout.js' -import type { Keyboard, Mouse } from 'puppeteer-core/lib/esm/puppeteer/api/Input.js' - -import getElementRect from './getElementRect.js' -import getWindowRect from './getWindowRect.js' -import { ELEMENT_KEY, UNICODE_CHARACTERS } from '../constants.js' -import { sleep } from '../utils.js' -import type DevToolsDriver from '../devtoolsdriver.js' - -const KEY = 'key' -const POINTER = 'pointer' - -interface Action { - duration?: number - type: string - value?: string - x?: number - y?: number - deltaX?: number - deltaY?: number - button?: number - origin?: any -} - -interface ActionsParameter { - type?: string - actions: Action[] - parameters?: { - pointerType?: string - } -} - -/** - * 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[] } -) { - const page = this.getPageHandle() - const lastPointer: { - x?: number, - y?: number - } = {} - - /** - * see https://github.com/jlipps/simple-wd-spec#input-sources-and-corresponding-actions - * for details on the `actions` format - */ - for (const action of actions) { - if (action.type === null || action.type === 'null') { - for (const singleAction of action.actions) { - await sleep(singleAction.duration) - } - continue - } - - if (action.type === 'key') { - const skipChars = [] - for (const singleAction of action.actions) { - if (singleAction.type === 'pause') { - await sleep(singleAction.duration) - continue - } - - const cmd = singleAction.type.slice(KEY.length).toLowerCase() as keyof Keyboard - const keyboardFn = (page.keyboard[cmd] as Function).bind(page.keyboard) - - /** - * skip up event as we had to use sendCharacter for non unicode - * characters which includes the up event already - */ - if (cmd === 'up' && skipChars[0] === singleAction.value) { - skipChars.shift() - continue - } - - /** - * for special characters like emojis 😉 we need to - * send in the value as text because it is not unicode - */ - const [key] = (Object.entries(UNICODE_CHARACTERS) - .find(([, charValue]) => charValue === singleAction.value) || []) as [KeyInput | undefined, never] - const pptrKey = key && _keyDefinitions[key] - ? key - : _keyDefinitions[singleAction.value as unknown as KeyInput] - ? singleAction.value as KeyInput - : undefined - if (!pptrKey) { - await page.keyboard.sendCharacter(singleAction.value as unknown as KeyInput) - skipChars.push(singleAction.value) - continue - } - - await keyboardFn(pptrKey) - continue - } - continue - } - - if (action.type === 'pointer') { - if (action.parameters && action.parameters.pointerType && action.parameters.pointerType !== 'mouse') { - throw new Error('Currently only "mouse" is supported as pointer type') - } - - /** - * detect double click - */ - if ( - action.actions.length === 6 && - action.actions[0].type === 'pointerMove' && - action.actions[1].type === 'pointerDown' && - action.actions[2].type === 'pointerUp' && - action.actions[3].type === 'pause' && - action.actions[4].type === 'pointerDown' && - action.actions[5].type === 'pointerUp' - ) { - let x = action.actions[0].x || 0 - let y = action.actions[0].y || 0 - if (action.actions[0].origin) { - const location = await getElementRect.call(this, { elementId: action.actions[0].origin[ELEMENT_KEY] }) - x += location.x + (location.width / 2) - y += location.y + (location.height / 2) - } - - await page.mouse.click(x, y, { clickCount: 2 }) - continue - } - - for (const singleAction of action.actions) { - if (singleAction.type === 'pause') { - await sleep(singleAction.duration) - continue - } - - const cmd = singleAction.type.slice(POINTER.length).toLowerCase() - const keyboardFn = (page.mouse[cmd as keyof Mouse] as Function).bind(page.mouse) - const { duration, button, origin } = singleAction - - let { x, y } = singleAction - if (cmd === 'move') { - /** - * set location relative from last position if origin is set to pointer - */ - if ( - typeof x === 'number' && - typeof y === 'number' && - origin === 'pointer' && - lastPointer.x && lastPointer.y - ) { - x += lastPointer.x - y += lastPointer.y - } - - /** - * set location relative from an element - */ - if (origin && typeof origin[ELEMENT_KEY] === 'string' && typeof x === 'number' && typeof y === 'number') { - const elemRect = await getElementRect.call(this, { elementId: origin[ELEMENT_KEY] }) - x += elemRect.x + (elemRect.width / 2) - y += elemRect.y + (elemRect.height / 2) - } - - lastPointer.x = x - lastPointer.y = y - await keyboardFn(x, y, { steps: 10 }) - continue - } else { - /** - * "left" is default button - * "1": middle, "2": right - */ - const pptrButton = ( - button === 1 ? 'middle' : ( - button === 2 ? 'right' : 'left' - ) - ) - await keyboardFn({ button: pptrButton }) - } - - if (duration) { - await sleep(duration) - } - continue - } - continue - } - - if (action.type === 'wheel') { - for (const singleAction of action.actions) { - const deltaX = singleAction.deltaX || 0 - const deltaY = singleAction.deltaY || 0 - - if (singleAction.origin) { - const windowSize = await getWindowRect.call(this) - const location = await getElementRect.call(this, { elementId: singleAction.origin[ELEMENT_KEY] }) - await page.mouse.wheel({ - deltaX: location.x + deltaX, - deltaY: location.y - windowSize.height + deltaY - }) - } else if (singleAction.x || singleAction.y) { - await page.mouse.wheel({ - deltaX: (singleAction.x || 0) + deltaX, - deltaY: (singleAction.y || 0) + deltaY - }) - } else { - await page.mouse.wheel({ deltaX, deltaY }) - } - } - continue - } - - throw new Error(`Unknown action type ("${action.type}"), allowed are only: null, key and pointer`) - } -} diff --git a/packages/devtools/src/commands/refresh.ts b/packages/devtools/src/commands/refresh.ts deleted file mode 100644 index 242a978a0..000000000 --- a/packages/devtools/src/commands/refresh.ts +++ /dev/null @@ -1,15 +0,0 @@ -import type DevToolsDriver from '../devtoolsdriver.js' - -/** - * 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 - */ -export default async function refresh (this: DevToolsDriver) { - delete this.currentFrame - - const page = this.getPageHandle() - await page.reload() - return null -} diff --git a/packages/devtools/src/commands/releaseActions.ts b/packages/devtools/src/commands/releaseActions.ts deleted file mode 100644 index 279034cbc..000000000 --- a/packages/devtools/src/commands/releaseActions.ts +++ /dev/null @@ -1,11 +0,0 @@ -/** - * 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. - * - * 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 - */ -export default async function performActions () {} diff --git a/packages/devtools/src/commands/sendAlertText.ts b/packages/devtools/src/commands/sendAlertText.ts deleted file mode 100644 index b9aec0b0b..000000000 --- a/packages/devtools/src/commands/sendAlertText.ts +++ /dev/null @@ -1,21 +0,0 @@ -import type DevToolsDriver from '../devtoolsdriver.js' - -/** - * The Send Alert Text command sets the text field of a window.prompt user prompt to the given value. - * - * @alias browser.sendAlertText - * @see https://w3c.github.io/webdriver/#dfn-send-alert-text - * @param {string} text string to set the prompt to - */ -export default async function sendAlertText ( - this: DevToolsDriver, - { text }: { text: string }) { - if (!this.activeDialog) { - throw new Error('no such alert') - } - - await this.activeDialog.accept(text) - delete this.activeDialog - - return null -} diff --git a/packages/devtools/src/commands/setTimeouts.ts b/packages/devtools/src/commands/setTimeouts.ts deleted file mode 100644 index a14208074..000000000 --- a/packages/devtools/src/commands/setTimeouts.ts +++ /dev/null @@ -1,19 +0,0 @@ -import type DevToolsDriver from '../devtoolsdriver.js' - -/** - * 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. - * - * @alias browser.setTimeouts - * @see https://w3c.github.io/webdriver/#dfn-set-timeouts - * @param {number} implicit integer in ms for session implicit wait timeout - * @param {number} pageLoad integer in ms for session page load timeout - * @param {number} script integer in ms for session script timeout - */ -export default async function setTimeouts ( - this: DevToolsDriver, - { implicit, pageLoad, script }: { implicit: number, pageLoad: number, script: number } -) { - await this.setTimeouts(implicit, pageLoad, script) - return null -} diff --git a/packages/devtools/src/commands/setWindowRect.ts b/packages/devtools/src/commands/setWindowRect.ts deleted file mode 100644 index 795255e0b..000000000 --- a/packages/devtools/src/commands/setWindowRect.ts +++ /dev/null @@ -1,22 +0,0 @@ -import type DevToolsDriver from '../devtoolsdriver.js' - -/** - * The Set Window Rect command alters the size and the position of the operating system window - * corresponding to the current top-level browsing context. - * - * @alias browser.setWindowRect - * @see https://w3c.github.io/webdriver/#dfn-set-window-rect - * @param {number} x the screenX attribute of the window object - * @param {number} y the screenY attribute of the window object - * @param {number} width the width of the outer dimensions of the top-level browsing context, including browser chrome etc... - * @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. - */ -export default async function setWindowRect ( - this: DevToolsDriver, - params: { width: number, height: number } -) { - const page = this.getPageHandle() - await page.setViewport(params) - return { width: params.width, height: params.height } -} diff --git a/packages/devtools/src/commands/status.ts b/packages/devtools/src/commands/status.ts deleted file mode 100644 index 4839578cf..000000000 --- a/packages/devtools/src/commands/status.ts +++ /dev/null @@ -1,34 +0,0 @@ -import fs from 'node:fs/promises' -import url from 'node:url' -import path from 'node:path' -import { resolve } from 'import-meta-resolve' - -let puppeteerVersion: string - -/** - * 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 - * that is specific to the implementation. - * - * @alias browser.status - * @see https://w3c.github.io/webdriver/#dfn-status - * @return {Object} returning an object with the Puppeteer version being used - */ -export default async function status () { - if (!puppeteerVersion) { - const puppeteerPath = await resolve('puppeteer-core', import.meta.url) - try { - const pkgJsonPath = path.resolve(url.fileURLToPath(puppeteerPath), '..', '..', '..', '..', 'package.json') - const pkgJson = JSON.parse((await fs.readFile(pkgJsonPath, 'utf-8')).toString()) - puppeteerVersion = pkgJson.version - } catch (err) { - // ignore - } - } - - return { - message: '', - ready: true, - puppeteerVersion - } -} diff --git a/packages/devtools/src/commands/switchToFrame.ts b/packages/devtools/src/commands/switchToFrame.ts deleted file mode 100644 index 5c74df02e..000000000 --- a/packages/devtools/src/commands/switchToFrame.ts +++ /dev/null @@ -1,85 +0,0 @@ -import type { Page } from 'puppeteer-core/lib/esm/puppeteer/api/Page.js' -import type { Frame } from 'puppeteer-core/lib/esm/puppeteer/api/Frame.js' -import type { ElementReference } from '@wdio/protocols' - -import { ELEMENT_KEY } from '../constants.js' -import { getStaleElementError } from '../utils.js' -import type DevToolsDriver from '../devtoolsdriver.js' - -type FrameIdParameter = { id: null | number | ElementReference } - -/** - * 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 - * browsing context for subsequent commands. - * - * @alias browser.switchToFrame - * @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`. - */ -export default async function switchToFrame ( - this: DevToolsDriver, - { id }: FrameIdParameter -) { - const page = this.getPageHandle(true) as unknown as Frame - - /** - * switch to parent frame - */ - if (id === null) { - /** - * if we are already in the parent frame, don't do anything - */ - if (typeof page.parentFrame !== 'function') { - return { id: null } - } - - let parentFrame = await page.parentFrame() - while (parentFrame) { - parentFrame = await parentFrame.parentFrame() - } - this.currentFrame = parentFrame as unknown as Page - return { id: null } - } - - /** - * switch frame by element ID - */ - if (typeof id === 'object' && typeof (id as ElementReference)[ELEMENT_KEY] === 'string') { - const elementHandle = await this.elementStore.get(id[ELEMENT_KEY]) - - if (!elementHandle) { - throw getStaleElementError(id[ELEMENT_KEY]) - } - - const contentFrame = await elementHandle.contentFrame() - - if (!contentFrame) { - throw new Error('no such frame') - } - - this.currentFrame = contentFrame as unknown as Page - return { id: id[ELEMENT_KEY] } - } - - /** - * switch frame by number - */ - if (typeof id === 'number') { - /** - * `page` has `frames` method while `frame` has `childFrames` method - */ - const getFrames = (page as unknown as Page).frames || page.childFrames - const childFrames = await getFrames.apply(page) - const childFrame = childFrames[id] - - if (!childFrame) { - throw new Error('no such frame') - } - - this.currentFrame = childFrame as unknown as Page - return { id: childFrame._id } - } - - throw new Error(`Could not switch frame, unknown id: ${id}`) -} diff --git a/packages/devtools/src/commands/switchToParentFrame.ts b/packages/devtools/src/commands/switchToParentFrame.ts deleted file mode 100644 index 7b3d5235c..000000000 --- a/packages/devtools/src/commands/switchToParentFrame.ts +++ /dev/null @@ -1,28 +0,0 @@ -import type { Page } from 'puppeteer-core/lib/esm/puppeteer/api/Page.js' -import type { Frame } from 'puppeteer-core/lib/esm/puppeteer/api/Frame.js' -import type DevToolsDriver from '../devtoolsdriver.js' - -/** - * The Switch to Parent Frame command sets the current browsing context for future commands - * to the parent of the current browsing context. - * - * @alias browser.switchToParentFrame - * @see https://w3c.github.io/webdriver/#dfn-switch-to-parent-frame - */ -export default async function switchToParentFrame (this: DevToolsDriver) { - const page = this.getPageHandle(true) as unknown as Frame - - /** - * check if we can access child frames, if now we are already in the - * parent browsing context - */ - if (typeof page.parentFrame !== 'function') { - return null - } - - /** - * ToDo(Christian): investigate why we interchangeably use Page and Frames here - */ - this.currentFrame = await page.parentFrame() as unknown as Page - return null -} diff --git a/packages/devtools/src/commands/switchToWindow.ts b/packages/devtools/src/commands/switchToWindow.ts deleted file mode 100644 index 78a675878..000000000 --- a/packages/devtools/src/commands/switchToWindow.ts +++ /dev/null @@ -1,26 +0,0 @@ -import type DevToolsDriver from '../devtoolsdriver.js' - -/** - * 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. - * - * @alias browser.switchToWindow - * @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 - */ -export default async function switchToWindow ( - this: DevToolsDriver, - { handle }: { handle: string } -) { - if (!this.windows.has(handle)) { - throw new Error(`window with handle ${handle} not found`) - } - - delete this.currentFrame - this.currentWindowHandle = handle - const page = this.getPageHandle() - page.on('dialog', this.dialogHandler.bind(this)) - await page.bringToFront() - - return handle -} diff --git a/packages/devtools/src/commands/takeElementScreenshot.ts b/packages/devtools/src/commands/takeElementScreenshot.ts deleted file mode 100644 index fac953446..000000000 --- a/packages/devtools/src/commands/takeElementScreenshot.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { getStaleElementError } from '../utils.js' -import type DevToolsDriver from '../devtoolsdriver.js' - -/** - * The Take Element Screenshot command takes a screenshot of the visible region - * encompassed by the bounding rectangle of an element. - * - * @alias browser.takeElementScreenshot - * @see https://w3c.github.io/webdriver/#dfn-take-element-screenshot - * @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. - */ -export default async function takeElementScreenshot ( - this: DevToolsDriver, - { elementId }: { elementId: string } -) { - const elementHandle = await this.elementStore.get(elementId) - - if (!elementHandle) { - throw getStaleElementError(elementId) - } - - return elementHandle.screenshot({ - captureBeyondViewport: false, - encoding: 'base64', - type: 'png', - }) -} diff --git a/packages/devtools/src/commands/takeScreenshot.ts b/packages/devtools/src/commands/takeScreenshot.ts deleted file mode 100644 index 4e4a98ac0..000000000 --- a/packages/devtools/src/commands/takeScreenshot.ts +++ /dev/null @@ -1,18 +0,0 @@ -import type DevToolsDriver from '../devtoolsdriver.js' - -/** - * The Take Screenshot command takes a screenshot of the top-level browsing context's viewport. - * - * @alias browser.takeScreenshot - * @see https://w3c.github.io/webdriver/#dfn-take-screenshot - * @return {string} The base64-encoded PNG image data comprising the screenshot of the initial viewport. - */ -export default async function takeScreenshot (this: DevToolsDriver) { - const page = this.getPageHandle() - return page.screenshot({ - captureBeyondViewport: false, - encoding: 'base64', - fullPage: false, // limit to viewport - type: 'png' - }) -} diff --git a/packages/devtools/src/constants.ts b/packages/devtools/src/constants.ts deleted file mode 100644 index 7102e5a7e..000000000 --- a/packages/devtools/src/constants.ts +++ /dev/null @@ -1,186 +0,0 @@ -import type { Options } from '@wdio/types' - -export const DEFAULT_WIDTH = 1200 -export const DEFAULT_HEIGHT = 900 -export const DEFAULT_X_POSITION = 0 -export const DEFAULT_Y_POSITION = 0 - -export const ELEMENT_KEY = 'element-6066-11e4-a52e-4f735466cecf' -export const SHADOW_ELEMENT_KEY = 'shadow-6066-11e4-a52e-4f735466cecf' - -// https://github.com/puppeteer/puppeteer/blob/083ea41e943e2a20014279fcfceb23c98a1e4491/src/node/Launcher.ts#L168 -export const DEFAULT_FLAGS = [ - // suppresses Save Password prompt window - '--enable-automation', - // do not block popups - '--disable-popup-blocking', - // Disable all chrome extensions entirely - '--disable-extensions', - // Disable various background network services, including extension updating, - // safe browsing service, upgrade detector, translate, UMA - '--disable-background-networking', - '--disable-background-timer-throttling', - '--disable-backgrounding-occluded-windows', - // Disable syncing to a Google account - '--disable-sync', - // Disable reporting to UMA, but allows for collection - '--metrics-recording-only', - // Disable installation of default apps on first run - '--disable-default-apps', - // Mute any audio - '--mute-audio', - // Skip first run wizards - '--no-first-run', - '--no-default-browser-check', - // chromedriver flags - '--disable-hang-monitor', - '--disable-prompt-on-repost', - '--disable-client-side-phishing-detection', - '--password-store=basic', - '--use-mock-keychain', - '--disable-component-extensions-with-background-pages', - '--disable-breakpad', - '--disable-dev-shm-usage', - '--disable-ipc-flooding-protection', - '--disable-renderer-backgrounding', - '--force-fieldtrials=*BackgroundTracing/default/', - '--enable-features=NetworkService,NetworkServiceInProcess', - /** - * `site-per-process` affects `page.frames()`, see #4471 - * `TranslateUI` disables built-in Google Translate service - */ - '--disable-features=site-per-process,TranslateUI,BlinkGenPropertyTrees' -] - -export const CHROME_NAMES = ['chrome', 'googlechrome', 'headlesschrome', 'google-chrome', 'chromium'] -export const FIREFOX_NAMES = ['firefox', 'ff', 'mozilla', 'mozillafirefox', 'headless firefox', 'headlessfirefox'] -export const EDGE_NAMES = ['edge', 'msedge', 'microsoft-edge', 'microsoftedge'] -export const SUPPORTED_BROWSER = [...CHROME_NAMES, ...FIREFOX_NAMES, ...EDGE_NAMES] - -export const BROWSER_TYPE: { - chrome: 'chrome', - firefox: 'firefox', - edge: 'edge' -} = { - chrome: 'chrome', - firefox: 'firefox', - edge: 'edge' -} - -export const DEFAULTS: Options.Definition = { - capabilities: { - type: 'object', - required: true - }, - /** - * Level of logging verbosity - */ - logLevel: { - type: 'string', - default: 'info', - match: /(trace|debug|info|warn|error|silent)/ - }, - /** - * directory for log files - */ - outputDir: { - type: 'string' - }, - /** - * maxConnectionRetries in chrome-launcher - */ - connectionRetryCount: { - type: 'number', - default: 50 - } -} - -export const DEFAULT_IMPLICIT_TIMEOUT = 0 -export const DEFAULT_PAGELOAD_TIMEOUT = 5 * 60 * 1000 // 5 min -export const DEFAULT_SCRIPT_TIMEOUT = 30 * 1000 // 30s - -export const SUPPORTED_SELECTOR_STRATEGIES = ['css selector', 'tag name', 'xpath', 'link text', 'partial link text', 'shadow'] -export const SERIALIZE_PROPERTY = 'data-devtoolsdriver-fetchedElement' -export const SERIALIZE_FLAG = '__executeElement' - -export const PPTR_LOG_PREFIX = 'puppeteer:protocol' - -export const ERROR_MESSAGES = { - staleElement: { - name: 'stale element reference', - message: 'stale element reference: The element reference is stale; either the element is no longer attached to the DOM, it is not in the current frame context, or the document has been refreshed' - } -} - -export const VENDOR_PREFIX: { - chrome: 'goog:chromeOptions', - 'chrome headless': 'goog:chromeOptions', - firefox: 'moz:firefoxOptions', - edge: 'ms:edgeOptions' -} = { - chrome: 'goog:chromeOptions', - 'chrome headless': 'goog:chromeOptions', - firefox: 'moz:firefoxOptions', - edge: 'ms:edgeOptions' -} - -/** - * unicode chars mapped to Puppeteer key map - */ -export const UNICODE_CHARACTERS = { - 'Cancel': '\uE001', - 'Help': '\uE002', - 'Backspace': '\uE003', - 'Tab': '\uE004', - 'Clear': '\uE005', - 'Enter': '\uE006', - 'NumpadEnter': '\uE007', - 'ShiftLeft': '\uE008', - 'ControlLeft': '\uE009', - 'ControlRight': '\uE051', - 'AltLeft': '\uE00A', - 'Pause': '\uE00B', - 'Escape': '\uE00C', - 'Space': '\uE00D', - 'PageUp': '\uE00E', - 'PageDown': '\uE00F', - 'End': '\uE010', - 'Home': '\uE011', - 'ArrowLeft': '\uE012', - 'ArrowUp': '\uE013', - 'ArrowRight': '\uE014', - 'ArrowDown': '\uE015', - 'Insert': '\uE016', - 'Delete': '\uE017', - 'Semicolon': '\uE018', - '=': '\uE019', - 'Numpad0': '\uE01A', - 'Numpad1': '\uE01B', - 'Numpad2': '\uE01C', - 'Numpad3': '\uE01D', - 'Numpad4': '\uE01E', - 'Numpad5': '\uE01F', - 'Numpad6': '\uE020', - 'Numpad7': '\uE021', - 'Numpad8': '\uE022', - 'Numpad9': '\uE023', - 'NumpadMultiply': '\uE024', - 'NumpadAdd': '\uE025', - 'Separator': '\uE026', - 'NumpadSubtract': '\uE027', - 'NumpadDecimal': '\uE028', - 'NumpadDivide': '\uE029', - 'F1': '\uE031', - 'F2': '\uE032', - 'F3': '\uE033', - 'F4': '\uE034', - 'F5': '\uE035', - 'F6': '\uE036', - 'F7': '\uE037', - 'F8': '\uE038', - 'F9': '\uE039', - 'F10': '\uE03A', - 'F11': '\uE03B', - 'F12': '\uE03C', - 'MetaLeft': '\uE03D' -} diff --git a/packages/devtools/src/devtoolsdriver.ts b/packages/devtools/src/devtoolsdriver.ts deleted file mode 100644 index 8942f316c..000000000 --- a/packages/devtools/src/devtoolsdriver.ts +++ /dev/null @@ -1,328 +0,0 @@ -import fs from 'node:fs' -import url from 'node:url' -import path from 'node:path' -import type { EventEmitter } from 'node:events' - -import { v4 as uuidv4 } from 'uuid' - -import logger from '@wdio/logger' -import type { CommandEndpoint } from '@wdio/protocols' - -import type { Browser } from 'puppeteer-core/lib/esm/puppeteer/api/Browser.js' -import type { Dialog } from 'puppeteer-core/lib/esm/puppeteer/api/Dialog.js' -import type { Page } from 'puppeteer-core/lib/esm/puppeteer/api/Page.js' -import type { Target } from 'puppeteer-core/lib/esm/puppeteer/api/Target.js' -import type { Frame } from 'puppeteer-core/lib/esm/puppeteer/api/Frame.js' - -import * as commands from './commands/index.js' -import ElementStore from './elementstore.js' -import { validate, sanitizeError } from './utils.js' -import { DEFAULT_IMPLICIT_TIMEOUT, DEFAULT_PAGELOAD_TIMEOUT, DEFAULT_SCRIPT_TIMEOUT } from './constants.js' -import type { ActiveListener } from './types.js' - -const __dirname = path.dirname(url.fileURLToPath(import.meta.url)) -const log = logger('devtools') - -export default class DevToolsDriver { - commands: Record = {} - elementStore = new ElementStore() - windows: Map = new Map() - timeouts: Map = new Map() - activeDialog?: Dialog = undefined - - browser: Browser - currentFrame?: Page - currentWindowHandle?: string - currentFrameUrl?: string - activeListeners: ActiveListener[] = [] - constructor(browser: Browser, pages: Page[]) { - this.browser = browser - - const dir = path.resolve(__dirname, 'commands') - const files = fs.readdirSync(dir).filter( - (file) => ( - file.endsWith('.js') || - ( - file.endsWith('.ts') && - !file.endsWith('.d.ts') - ) - ) - ) - - for (const filename of files) { - const commandName = path.basename(filename, path.extname(filename)) - - if (!commandName) { - throw new Error('Couldn\'t determine command name') - } - - this.commands[commandName] = commands[commandName as keyof typeof commands] - } - - this.initBrowser(browser, pages) - } - - private _createWindowHandle (page: Page) { - const pageId = uuidv4() - this.windows.set(pageId, page) - this.currentFrame = page - this.currentWindowHandle = pageId - } - - private async _targetCreatedHandler (target: Target) { - const page = await target.page() - - if (!page) { - return - } - - this._createWindowHandle(page) - } - - private async _targetDestroyedHandler (target: Target) { - const page = await target.page() - for (const [pageId, p] of this.windows.entries()) { - if (page !== p) { - continue - } - - log.trace(`Target destroyed, removing window handle ${pageId}`) - this.windows.delete(pageId) - break - } - - const pageIds = [...this.windows.keys()] - this.currentFrame = this.windows.get(pageIds[0]) - this.currentWindowHandle = pageIds[0] - log.trace(`Switching to window handle with id ${pageIds[0]}`) - } - - private addListener(emitter: ActiveListener['emitter'], eventName: string, handler: any) { - const boundHandler = handler.bind(this) - emitter.on(eventName, boundHandler) - this.activeListeners.push({ emitter, eventName, boundHandler }) - } - - private cleanupListeners() { - this.activeListeners.forEach(({ emitter, eventName, boundHandler }) => { - emitter.off(eventName, boundHandler) - }) - this.activeListeners = [] - } - - /** - * Inits browser listeners and sets initial handlers for given pages. - * Function is also intended to be used while reloading DevTools session. - * @param browser Puppeteer Browser - * @param pages Puppeteer page array - */ - initBrowser(browser: Browser, pages: Page[]) { - this.cleanupListeners() - this.elementStore = new ElementStore() - this.windows = new Map() - this.activeDialog = undefined - this.browser = browser - - this.addListener(this.browser, 'targetcreated', this._targetCreatedHandler) - this.addListener(this.browser, 'targetdestroyed', this._targetDestroyedHandler) - - for (const page of pages) { - this._createWindowHandle(page) - } - - /** - * set default timeouts - */ - this.setTimeouts(DEFAULT_IMPLICIT_TIMEOUT, DEFAULT_PAGELOAD_TIMEOUT, DEFAULT_SCRIPT_TIMEOUT) - - const page = this.getPageHandle() - if (page) { - this.addListener(page, 'dialog', this.dialogHandler) - this.addListener(page, 'framenavigated', this.framenavigatedHandler) - } - } - - register(commandInfo: CommandEndpoint) { - const self = this - const { command, ref, parameters, variables = [] } = commandInfo - - /** - * check if command is implemented - */ - if (typeof this.commands[command] !== 'function') { - return () => { - throw new Error( - `Command "${command}" is not supported using the Devtools protocol. ` + - 'You might want to use WebDriver as automation engine. For more ' + - 'information check out https://webdriver.io/docs/automationProtocols!' - ) - } - } - - /** - * within here you find the webdriver scope - */ - let retries = 0 - const wrappedCommand = async function (this: EventEmitter, ...args: any[]): Promise { - await self.checkPendingNavigations() - const params = validate(command, parameters, variables as any, ref, args) - let result - - try { - this.emit('command', { command, params, retries }) - result = await self.commands[command].call(self, params) - } catch (err: any) { - /** - * if though we check for an execution context before executing a command we - * can technically still run into the situation (especially if the command - * contains multiple interaction with the page and is long) where the execution - * context gets destroyed. For these cases handle page transitions gracefully - * by repeating the command. - */ - if (err.message.includes('most likely because of a navigation')) { - log.debug('Command failed due to unfinished page transition, retrying...') - const page = self.getPageHandle() - - ++retries - - // In some cases, the page load event never arrives - // Wait for a page load event only after trying to execute the command a few times - if (retries >= 3) { - await new Promise((resolve, reject) => { - const pageloadTimeout = setTimeout(() => reject(new Error('page load timeout')), self.timeouts.get('pageLoad')) - page.once('load', () => { - clearTimeout(pageloadTimeout) - resolve() - }) - }) - } - - return wrappedCommand.apply(this, args) - } - - throw sanitizeError(err) - } - - this.emit('result', { command, params, retries, result: { value: result } }) - if (typeof result !== 'undefined') { - const isScreenshot = ( - command.toLowerCase().includes('screenshot') && - typeof result === 'string' && - result.length > 64 - ) - log.info('RESULT', isScreenshot ? `${result.substr(0, 61)}...` : result) - } - - return result - } - - return wrappedCommand - } - - dialogHandler(dialog: Dialog) { - this.activeDialog = dialog - } - - framenavigatedHandler(frame: Frame) { - this.currentFrameUrl = frame.url() - this.elementStore.clear(frame.parentFrame() ? frame : undefined) - } - - setTimeouts(implicit?: number, pageLoad?: number, script?: number) { - if (typeof implicit === 'number') { - this.timeouts.set('implicit', implicit) - } - if (typeof pageLoad === 'number') { - this.timeouts.set('pageLoad', pageLoad) - } - if (typeof script === 'number') { - this.timeouts.set('script', script) - } - - const page = this.getPageHandle() - const pageloadTimeout = this.timeouts.get('pageLoad') - if (page && pageloadTimeout) { - page.setDefaultTimeout(pageloadTimeout) - } - } - - getPageHandle (isInFrame = false) { - if (isInFrame && this.currentFrame) { - return this.currentFrame - } - - if (!this.currentWindowHandle) { - throw new Error('no current window handle registered') - } - - const pageHandle = this.windows.get(this.currentWindowHandle) - - if (!pageHandle) { - throw new Error('Couldn\'t find page handle') - } - - return pageHandle - } - - async checkPendingNavigations (pendingNavigationStart = Date.now()) { - /** - * ensure there is no page transition happening and an execution context - * is available - */ - let page = this.getPageHandle() - - /** - * ignore pending navigation check if dialog is open - * or there are no pages - */ - if (this.activeDialog || !page) { - return - } - - /** - * if current page is a frame we have to get the page from the browser - * that has this frame listed - */ - if (!page.mainFrame) { - const pages = await this.browser.pages() - const mainFrame = pages.find((browserPage) => ( - browserPage.frames().find((frame: unknown) => page === frame) - )) - - if (mainFrame) { - page = mainFrame - } - } - - const pageloadTimeout = this.timeouts.get('pageLoad') - const pageloadTimeoutReached = typeof pageloadTimeout !== 'undefined' - ? Date.now() - pendingNavigationStart > pageloadTimeout - : false - - try { - const executionContext = await page.mainFrame() - await executionContext.evaluate('1') - - /** - * if we have an execution context, also check for the ready state - */ - const readyState = await executionContext.evaluate('document.readyState') - if (readyState === 'complete' || pageloadTimeoutReached) { - return - } - } catch (err: any) { - /** - * throw original error if a context could not be established - */ - if (pageloadTimeoutReached) { - throw err - } - } - - /*** - * Avoid looping so quickly we run out of memory before the timeout. - */ - await new Promise(resolve => setTimeout(resolve, Math.min(100, typeof pageloadTimeout === 'number' ? pageloadTimeout / 10 : 100))) - await this.checkPendingNavigations(pendingNavigationStart) - } -} diff --git a/packages/devtools/src/elementstore.ts b/packages/devtools/src/elementstore.ts deleted file mode 100644 index 33610bd74..000000000 --- a/packages/devtools/src/elementstore.ts +++ /dev/null @@ -1,52 +0,0 @@ -import type { ElementHandle } from 'puppeteer-core/lib/esm/puppeteer/api/ElementHandle.js' -import type { Frame } from 'puppeteer-core/lib/esm/puppeteer/api/Frame.js' - -export default class ElementStore { - private _index = 0 - private _elementMap: Map = new Map() - private _frameMap: Map> = new Map() - - set (elementHandle: ElementHandle) { - const index = `ELEMENT-${++this._index}` - this._elementMap.set(index, elementHandle) - const frame = elementHandle.frame - if (frame) { - let elementIndexes = this._frameMap.get(frame) - if (!elementIndexes) { - elementIndexes = new Set() - this._frameMap.set(frame, elementIndexes) - } - elementIndexes.add(index) - } - return index - } - - async get (index: string) { - const elementHandle = this._elementMap.get(index) - - if (!elementHandle) { - return elementHandle - } - - const isElementAttachedToDOM = await elementHandle.evaluate((el: HTMLElement) => { - // https://developer.mozilla.org/en-US/docs/Web/API/Node/isConnected - return el.isConnected - }) - - return isElementAttachedToDOM ? elementHandle : undefined - } - - clear (frame?: Frame) { - if (!frame) { - this._elementMap.clear() - this._frameMap.clear() - return - } - - const elementIndexes = this._frameMap.get(frame) - if (elementIndexes) { - elementIndexes.forEach((elementIndex) => this._elementMap.delete(elementIndex)) - this._frameMap.delete(frame) - } - } -} diff --git a/packages/devtools/src/finder/edge.ts b/packages/devtools/src/finder/edge.ts deleted file mode 100644 index cad2d4a66..000000000 --- a/packages/devtools/src/finder/edge.ts +++ /dev/null @@ -1,141 +0,0 @@ -/* istanbul ignore file */ - -/** - * @license Copyright 2016 Google Inc. All Rights Reserved. - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. - */ -import os from 'node:os' -import path from 'node:path' -import { execSync } from 'node:child_process' -import { getEdgePath } from 'edge-paths' -import { canAccess } from '@wdio/utils/node' - -import { sort, findByWhich } from '../utils.js' -import { darwinGetAppPaths, darwinGetInstallations } from './finder.js' - -const newLineRegex = /\r?\n/ -const EDGE_BINARY_NAMES = ['edge', 'msedge', 'microsoft-edge', 'microsoft-edge-dev'] -const EDGE_REGEX = /((ms|microsoft))?edge/g - -function darwin() { - const suffixes = [ - '/Contents/MacOS/Microsoft Edge' - ] - - const appName = 'Microsoft Edge' - const defaultPath = `/Applications/${appName}.app${suffixes[0]}` - - let installations - if (canAccess(defaultPath)) { - installations = [defaultPath] - } else { - const appPaths = darwinGetAppPaths(appName) - installations = darwinGetInstallations(appPaths, suffixes) - } - - // Retains one per line to maintain readability. - // clang-format off - const priorities = [ - { regex: new RegExp(`^${process.env.HOME}/Applications/.*Microsoft Edge.app`), weight: 50 }, - { regex: /^\/Applications\/.*Microsoft Edge.app/, weight: 100 }, - { regex: /^\/Volumes\/.*Microsoft Edge.app/, weight: -2 } - ] - - const whichFinds = findByWhich( - EDGE_BINARY_NAMES, - [{ regex: EDGE_REGEX, weight: 51 }] - ) - const installFinds = sort(installations, priorities) - return [...installFinds, ...whichFinds] -} - -/** - * Look for linux executables in 3 ways - * 1. Look into the directories where .desktop are saved on gnome based distros - * 2. Look for edge by using the which command - */ -function linux() { - let installations: string[] = [] - - // 1. Look into the directories where .desktop are saved on gnome based distros - const desktopInstallationFolders = [ - path.join(os.homedir(), '.local/share/applications/'), - '/usr/share/applications/', - ] - desktopInstallationFolders.forEach(folder => { - installations = installations.concat(findEdgeExecutables(folder)) - }) - - return findByWhich( - EDGE_BINARY_NAMES, - [{ regex: EDGE_REGEX, weight: 51 }] - ) -} - -function win32 () { - const installations: string[] = [] - const suffixes = [ - `${path.sep}Microsoft${path.sep}Edge${path.sep}Application${path.sep}edge.exe`, - `${path.sep}Microsoft${path.sep}Edge${path.sep}Application${path.sep}msedge.exe`, - `${path.sep}Microsoft${path.sep}Edge Dev${path.sep}Application${path.sep}msedge.exe` - ] - - const prefixes = [ - process.env.LOCALAPPDATA || '', process.env.PROGRAMFILES || '', process.env['PROGRAMFILES(X86)'] || '' - ].filter(Boolean) - - const checkedPath: string[] = [] - prefixes.forEach(prefix => suffixes.forEach(suffix => { - const edgePath = path.join(prefix, suffix) - checkedPath.push(edgePath) - if (canAccess(edgePath)) { - installations.push(edgePath) - } - })) - - /** - * fallback using edge-path - */ - if (installations.length === 0) { - const edgePath = getEdgePath() - if (canAccess(edgePath)) { - installations.push(edgePath) - } - } - - return installations -} - -function findEdgeExecutables(folder: string) { - const argumentsRegex = /(^[^ ]+).*/ // Take everything up to the first space - const edgeExecRegex = '^Exec=/.*/(edge)-.*' - - const installations: string[] = [] - if (canAccess(folder)) { - let execPaths - - // Some systems do not support grep -R so fallback to -r. - // See https://github.com/GoogleChrome/chrome-launcher/issues/46 for more context. - try { - execPaths = execSync( - `grep -ER "${edgeExecRegex}" ${folder} | awk -F '=' '{print $2}'`, { stdio: 'pipe' }) - } catch (err: any) { - execPaths = execSync( - `grep -Er "${edgeExecRegex}" ${folder} | awk -F '=' '{print $2}'`, { stdio: 'pipe' }) - } - - execPaths = execPaths.toString().split(newLineRegex).map( - (execPath) => execPath.replace(argumentsRegex, '$1')) - - execPaths.forEach((execPath) => canAccess(execPath) && installations.push(execPath)) - } - - return installations -} - -export default { - darwin, - linux, - win32 -} diff --git a/packages/devtools/src/finder/finder.ts b/packages/devtools/src/finder/finder.ts deleted file mode 100644 index 6e6285715..000000000 --- a/packages/devtools/src/finder/finder.ts +++ /dev/null @@ -1,35 +0,0 @@ -import path from 'node:path' -import { execSync } from 'node:child_process' - -import { canAccess } from '@wdio/utils/node' - -const DARWIN_LIST_APPS = 'system_profiler SPApplicationsDataType -json' - -interface ApplicationDataType { - SPApplicationsDataType: { - info: string - path: string - }[] -} - -export const darwinGetAppPaths = (app: string) => { - const apps: ApplicationDataType = JSON.parse(execSync(DARWIN_LIST_APPS).toString()) - const appPaths = apps.SPApplicationsDataType - .filter(inst => inst.info && inst.info.startsWith(app)) - .map(inst => inst.path) - - return appPaths -} - -export const darwinGetInstallations = (appPaths: string[], suffixes: string[]) => { - const installations: string[] = [] - appPaths.forEach((inst) => { - suffixes.forEach(suffix => { - const execPath = path.join(inst.substring(0, inst.indexOf('.app') + 4).trim(), suffix) - if (canAccess(execPath) && installations.indexOf(execPath) === -1) { - installations.push(execPath) - } - }) - }) - return installations -} diff --git a/packages/devtools/src/finder/firefox.ts b/packages/devtools/src/finder/firefox.ts deleted file mode 100644 index 3bf8978e9..000000000 --- a/packages/devtools/src/finder/firefox.ts +++ /dev/null @@ -1,131 +0,0 @@ -/* istanbul ignore file */ - -/** - * @license Copyright 2016 Google Inc. All Rights Reserved. - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. - */ -import os from 'node:os' -import path from 'node:path' -import { execSync } from 'node:child_process' -import { canAccess } from '@wdio/utils/node' - -import { sort, findByWhich } from '../utils.js' -import { darwinGetAppPaths, darwinGetInstallations } from './finder.js' - -const newLineRegex = /\r?\n/ - -export interface Priorities { - regex: RegExp - weight: number -} - -function darwin() { - const suffixes = [ - '/Contents/MacOS/firefox-bin' - ] - - const appName = 'Firefox Nightly' - const defaultPath = `/Applications/${appName}.app${suffixes[0]}` - - let installations - if (canAccess(defaultPath)) { - installations = [defaultPath] - } else { - const appPaths = darwinGetAppPaths(appName) - installations = darwinGetInstallations(appPaths, suffixes) - } - - /** - * Retains one per line to maintain readability. - */ - const priorities: Priorities[] = [ - { regex: new RegExp(`^${process.env.HOME}/Applications/.*Firefox.app`), weight: 50 }, - { regex: /^\/Applications\/.*Firefox.app/, weight: 100 }, - { regex: /^\/Volumes\/.*Firefox.app/, weight: -2 } - ] - - const whichFinds = findByWhich( - ['firefox-nightly', 'firefox-trunk'], - [{ regex: /firefox-nightly/, weight: 51 }] - ) - const installFinds = sort(installations, priorities) - return [...installFinds, ...whichFinds] -} - -/** - * Look for linux executables in 3 ways - * 1. Look into the directories where .desktop are saved on gnome based distros - * 2. Look for edge by using the which command - */ -function linux() { - let installations: string[] = [] - - // 1. Look into the directories where .desktop are saved on gnome based distros - const desktopInstallationFolders = [ - path.join(os.homedir(), '.local/share/applications/'), - '/usr/share/applications/', - ] - desktopInstallationFolders.forEach(folder => { - installations = installations.concat(findFirefoxExecutables(folder)) - }) - - const whichFinds = findByWhich( - ['firefox-nightly', 'firefox-trunk', 'firefox'], - [{ regex: /firefox/, weight: 51 }] - ) - return [...installations, ...whichFinds] -} - -function win32() { - const installations: string[] = [] - const suffixes = [ - `${path.sep}Firefox Nightly${path.sep}Application${path.sep}firefox.exe` - ] - - const prefixes = [ - process.env.LOCALAPPDATA || '', process.env.PROGRAMFILES || '', process.env['PROGRAMFILES(X86)'] || '' - ].filter(Boolean) - - prefixes.forEach(prefix => suffixes.forEach(suffix => { - const firefoxPath = path.join(prefix, suffix) - if (canAccess(firefoxPath)) { - installations.push(firefoxPath) - } - })) - - return installations -} - -function findFirefoxExecutables(folder: string) { - const argumentsRegex = /(^[^ ]+).*/ // Take everything up to the first space - const edgeExecRegex = '^Exec=/.*/(firefox)-.*' - - const installations: string[] = [] - if (canAccess(folder)) { - let execPaths - - // Some systems do not support grep -R so fallback to -r. - // See https://github.com/GoogleChrome/chrome-launcher/issues/46 for more context. - try { - execPaths = execSync( - `grep -ER "${edgeExecRegex}" ${folder} | awk -F '=' '{print $2}'`, { stdio: 'pipe' }) - } catch (err: any) { - execPaths = execSync( - `grep -Er "${edgeExecRegex}" ${folder} | awk -F '=' '{print $2}'`, { stdio: 'pipe' }) - } - - execPaths = execPaths.toString().split(newLineRegex).map( - (execPath) => execPath.replace(argumentsRegex, '$1')) - - execPaths.forEach((execPath) => canAccess(execPath) && installations.push(execPath)) - } - - return installations -} - -export default { - darwin, - linux, - win32 -} diff --git a/packages/devtools/src/finder/index.ts b/packages/devtools/src/finder/index.ts deleted file mode 100644 index 8b61c8d07..000000000 --- a/packages/devtools/src/finder/index.ts +++ /dev/null @@ -1,16 +0,0 @@ -import edgeFinder from './edge.js' -import firefoxFinder from './firefox.js' - -export default (browserName: 'firefox' | 'edge', platform: NodeJS.Platform) => { - const finder = { - firefox: firefoxFinder, - edge: edgeFinder - }[browserName] - - const supportedPlatforms = Object.keys(finder) as NodeJS.Platform[] - if (!supportedPlatforms.includes(platform)) { - throw new Error(`Operating system ("${process.platform}") is not supported`) - } - - return finder[platform as keyof typeof finder] -} diff --git a/packages/devtools/src/index.ts b/packages/devtools/src/index.ts deleted file mode 100644 index e499b0019..000000000 --- a/packages/devtools/src/index.ts +++ /dev/null @@ -1,186 +0,0 @@ -import os from 'node:os' -import path from 'node:path' -import UAParser from 'ua-parser-js' -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/esm/puppeteer/api/Browser.js' - -import DevToolsDriver from './devtoolsdriver.js' -import launch from './launcher.js' -import { DEFAULTS, SUPPORTED_BROWSER, VENDOR_PREFIX } from './constants.js' -import { getPrototype, patchDebug } from './utils.js' -import type { Client, AttachOptions, DevToolsOptions as WDIODevtoolsOptionsExtension } from './types.js' - -const log = logger('devtools:puppeteer') - -/** - * patch debug package to log Puppeteer CDP messages - */ -await patchDebug(log) - -export const sessionMap = new Map() - -export default class DevTools { - static async newSession ( - options: Options.WebDriver, - modifier?: Function, - userPrototype = {}, - customCommandWrapper?: Function - ): Promise { - const envLogLevel = process.env.WDIO_LOG_LEVEL as Options.WebDriverLogTypes | undefined - options.logLevel = envLogLevel ?? options.logLevel - const params = validateConfig(DEFAULTS, options) - - if (params.logLevel && (!options.logLevels || !(options.logLevels as any).devtools)) { - logger.setLevel('devtools', params.logLevel) - } - - /** - * Store all log events in a file - */ - if (params.outputDir && !process.env.WDIO_LOG_PATH) { - process.env.WDIO_LOG_PATH = path.join(params.outputDir, 'wdio.log') - } - - log.info('Initiate new session using the DevTools protocol') - - const requestedCapabilities = { ...params.capabilities } - const browser = await launch(params.capabilities as WebdriverIO.Capabilities) - const pages = await browser.pages() - const driver = new DevToolsDriver(browser, pages) - const sessionId = uuidv4() - const uaParser = new UAParser(await browser.userAgent()) - const userAgent = uaParser.getResult() - - /** - * find vendor key in capabilities - */ - type ValueOf = T[keyof T] - const availableVendorPrefixes = Object.values(VENDOR_PREFIX) - const vendorCapPrefix = Object.keys(params.capabilities as WebdriverIO.Capabilities) - .find( - (capKey: ValueOf) => availableVendorPrefixes.includes(capKey) - ) as keyof WebdriverIO.Capabilities - || - VENDOR_PREFIX[userAgent.browser.name?.toLocaleLowerCase() as keyof typeof VENDOR_PREFIX] - - const { browserName } = (requestedCapabilities as Capabilities.W3CCapabilities).alwaysMatch || requestedCapabilities - params.capabilities = { - browserName: (userAgent.browser.name || browserName || 'unknown').split(' ').shift()?.toLowerCase(), - browserVersion: userAgent.browser.version, - platformName: os.platform(), - platform: os.release() - } - - if (vendorCapPrefix) { - Object.assign(params.capabilities, { - [vendorCapPrefix]: Object.assign( - { debuggerAddress: browser.wsEndpoint().split('/')[2] }, - params.capabilities[vendorCapPrefix] - ) - }) - } - - sessionMap.set(sessionId, { browser, session: driver }) - const environmentPrototype: Record = {} - Object.entries(devtoolsEnvironmentDetector({ - browserName: userAgent?.browser?.name?.toLowerCase() - })).forEach(([name, value]) => { - environmentPrototype[name] = { value } - }) - const commandWrapper = ( - method: string, - endpoint: string, - commandInfo: CommandEndpoint - ) => driver.register(commandInfo) - const protocolCommands = getPrototype(commandWrapper) - const prototype = { - ...protocolCommands, - ...userPrototype, - ...environmentPrototype - } - - const monad = webdriverMonad( - { ...params, requestedCapabilities }, - modifier, - prototype - ) - return monad(sessionId, customCommandWrapper) - } - - /** - * Changes The instance session id and browser capabilities for the new session - * directly into the passed in browser object - * - * @param {object} instance the object we get from a new browser session. - * @returns {string} the new session id of the browser - */ - static async reloadSession (instance: Client, newCapabilities: WebdriverIO.Capabilities): Promise { - const { session } = sessionMap.get(instance.sessionId) - const browser = await launch({ - ...instance.requestedCapabilities as WebdriverIO.Capabilities, - ...(newCapabilities || {}) - }) - const pages = await browser.pages() - session.initBrowser.call(session, browser, pages) - instance.puppeteer = browser - sessionMap.set(instance.sessionId, { browser, session }) - return instance.sessionId - } - - /** - * allows user to attach to existing sessions - */ - static async attachToSession ( - options: AttachOptions, - modifier?: Function, - userPrototype = {}, - customCommandWrapper?: Function - ): Promise { - const browser = await launch(options.capabilities) - const pages = await browser.pages() - const driver = new DevToolsDriver(browser, pages) - const sessionId = uuidv4() - const uaParser = new UAParser(await browser.userAgent()) - const userAgent = uaParser.getResult() - - const environmentPrototype: Record = { puppeteer: { value: browser } } - Object.entries(devtoolsEnvironmentDetector({ - browserName: userAgent?.browser?.name?.toLowerCase() - })).forEach(([name, value]) => { - environmentPrototype[name] = { value } - }) - const commandWrapper = ( - method: string, - endpoint: string, - commandInfo: CommandEndpoint - ) => driver.register(commandInfo) - const protocolCommands = getPrototype(commandWrapper) - const prototype = { - ...protocolCommands, - ...userPrototype, - ...environmentPrototype - } - - const monad = webdriverMonad( - options, - modifier, - prototype - ) - return monad(sessionId, customCommandWrapper) - } -} - -export { SUPPORTED_BROWSER } -export * from './types.js' - -declare global { - namespace WebdriverIO { - interface WDIODevtoolsOptions extends WDIODevtoolsOptionsExtension {} - } -} diff --git a/packages/devtools/src/launcher.ts b/packages/devtools/src/launcher.ts deleted file mode 100644 index c2886dd35..000000000 --- a/packages/devtools/src/launcher.ts +++ /dev/null @@ -1,269 +0,0 @@ -import type { Options } from 'chrome-launcher' -import { launch as launchChromeBrowser } from 'chrome-launcher' -import type { PuppeteerLaunchOptions, ConnectOptions } from 'puppeteer-core' -import puppeteer, { KnownDevices, Puppeteer } from 'puppeteer-core' -import logger from '@wdio/logger' -import type { Browser } from 'puppeteer-core/lib/esm/puppeteer/api/Browser.js' -import type { Capabilities } from '@wdio/types' -import { QueryHandler } from 'query-selector-shadow-dom/plugins/puppeteer/index.js' - -import browserFinder from './finder/index.js' -import { getPages, launchChromeUsingWhich } from './utils.js' -import { - CHROME_NAMES, - FIREFOX_NAMES, - EDGE_NAMES, - BROWSER_TYPE, - DEFAULT_FLAGS, - DEFAULT_WIDTH, - DEFAULT_HEIGHT, - DEFAULT_X_POSITION, - DEFAULT_Y_POSITION, - VENDOR_PREFIX -} from './constants.js' - -const log = logger('devtools') - -const DEVICE_NAMES = Object.keys(KnownDevices) - -/** - * launches Chrome and returns a Puppeteer browser instance - * @param {object} capabilities session capabilities - * @return {object} puppeteer browser instance - */ -async function launchChrome (capabilities: WebdriverIO.Capabilities) { - const chromeOptions: Capabilities.ChromeOptions = capabilities[VENDOR_PREFIX.chrome] || {} - const mobileEmulation = chromeOptions.mobileEmulation || {} - const devtoolsOptions = capabilities['wdio:devtoolsOptions'] || {} - const chromeOptionsArgs = (chromeOptions.args || []).map((arg) => ( - arg.startsWith('--') ? arg : `--${arg}` - )) - - /** - * `ignoreDefaultArgs` and `headless` are currently expected to be part of the capabilities - * but we should move them into a custom capability object, e.g. `wdio:devtoolsOptions`. - * This should be cleaned up for v7 release - * ToDo(Christian): v7 cleanup - */ - const ignoreDefaultArgs = (capabilities as any).ignoreDefaultArgs || devtoolsOptions.ignoreDefaultArgs - const headless = (chromeOptions as any).headless || devtoolsOptions.headless - - if (typeof mobileEmulation.deviceName === 'string') { - const deviceProperties = KnownDevices[mobileEmulation.deviceName as keyof typeof KnownDevices] - - if (!deviceProperties) { - throw new Error(`Unknown device name "${mobileEmulation.deviceName}", available: ${DEVICE_NAMES.join(', ')}`) - } - - mobileEmulation.userAgent = deviceProperties.userAgent - mobileEmulation.deviceMetrics = { - width: deviceProperties.viewport.width, - height: deviceProperties.viewport.height, - pixelRatio: deviceProperties.viewport.deviceScaleFactor - } - } - - let userDataDir: string | boolean | undefined - const userDataDirIndex = chromeOptionsArgs.findIndex((arg) => arg.includes('user-data-dir')) - if (userDataDirIndex > -1) { - userDataDir = chromeOptionsArgs[userDataDirIndex].split('=').pop() as string - chromeOptionsArgs.splice(userDataDirIndex, 1) - } - - const defaultFlags = Array.isArray(ignoreDefaultArgs) ? DEFAULT_FLAGS.filter(flag => !ignoreDefaultArgs.includes(flag)) : (!ignoreDefaultArgs) ? DEFAULT_FLAGS : [] - const deviceMetrics = mobileEmulation.deviceMetrics || (devtoolsOptions.defaultViewport && { - width: devtoolsOptions.defaultViewport.width, - height: devtoolsOptions.defaultViewport.height, - pixelRatio: devtoolsOptions.defaultViewport.deviceScaleFactor, - touch: devtoolsOptions.defaultViewport.isMobile - }) || {} - const windowFlags = devtoolsOptions.defaultViewport !== null ? - [`--window-position=${DEFAULT_X_POSITION},${DEFAULT_Y_POSITION}`, `--window-size=${deviceMetrics?.width || DEFAULT_WIDTH},${deviceMetrics?.height || DEFAULT_HEIGHT}`] : [] - const chromeFlags = [ - ...defaultFlags, - ...windowFlags, - ...(headless ? [ - '--headless', - '--no-sandbox' - ] : []), - ...chromeOptionsArgs - ] - - if (typeof deviceMetrics.pixelRatio === 'number') { - chromeFlags.push(`--device-scale-factor=${deviceMetrics.pixelRatio}`) - } - - if (typeof mobileEmulation.userAgent === 'string') { - chromeFlags.push(`--user-agent=${mobileEmulation.userAgent}`) - } - - if (deviceMetrics?.touch) { - chromeFlags.push( - '--enable-touch-drag-drop', - '--touch-events', - '--enable-viewport' - ) - } - - // Honor both ignoreHTTPSErrors and acceptInsecureCerts - // Only for WebKit/Blink engines, Firefox uses a different option - if (capabilities.acceptInsecureCerts || devtoolsOptions.ignoreHTTPSErrors) { - chromeFlags.push('--ignore-certificate-errors') - } - - log.info(`Launch Google Chrome (${chromeOptions.binary}) with flags: ${chromeFlags.join(' ')}`) - const launchOptions: Options = { - prefs: chromeOptions.prefs, - chromePath: chromeOptions.binary, - ignoreDefaultFlags: true, - chromeFlags, - userDataDir, - envVars: devtoolsOptions.env, - ...(devtoolsOptions.customPort ? { port: devtoolsOptions.customPort } : {}) - } - const chrome = await launchChromeBrowser(launchOptions).catch( - (err: Error) => launchChromeUsingWhich(err, launchOptions)) - - log.info(`Connect Puppeteer with browser on port ${chrome.port}`) - const browser = await puppeteer.connect({ - ...chromeOptions, - ...devtoolsOptions, - defaultViewport: null, - browserURL: `http://127.0.0.1:${chrome.port}` - }) as unknown as Browser // casting from @types/puppeteer to built in type - - /** - * when using Chrome Launcher we have to close a tab as Puppeteer - * creates automatically a new one - */ - const pages = await getPages(browser) - for (const page of pages.slice(0, -1)) { - if (page.url() === 'about:blank') { - await page.close() - } - } - - return browser -} - -function launchBrowser (capabilities: WebdriverIO.Capabilities, browserType: 'edge' | 'firefox') { - const product = browserType === BROWSER_TYPE.firefox ? BROWSER_TYPE.firefox : BROWSER_TYPE.chrome - const vendorCapKey = VENDOR_PREFIX[browserType] - const devtoolsOptions = capabilities['wdio:devtoolsOptions'] || {} - - /** - * `ignoreDefaultArgs` and `headless` are currently expected to be part of the capabilities - * but we should move them into a custom capability object, e.g. `wdio:devtoolsOptions`. - * This should be cleaned up for v7 release - * ToDo(Christian): v7 cleanup - */ - const ignoreDefaultArgs = (capabilities as any).ignoreDefaultArgs || devtoolsOptions.ignoreDefaultArgs - const headless = (capabilities as any).headless || devtoolsOptions.headless - - // Set devtoolsOptions to honor both ignoreHTTPSErrors and acceptInsecureCerts - // Only necessary for Firefox, not for WebKit/Blink engines - devtoolsOptions.ignoreHTTPSErrors = browserType === 'firefox' - ? Boolean(devtoolsOptions.ignoreHTTPSErrors || capabilities.acceptInsecureCerts) - : devtoolsOptions.ignoreHTTPSErrors ?? false - - if (!capabilities[vendorCapKey]) { - capabilities[vendorCapKey] = {} - } - - const browserFinderMethod = browserFinder(browserType, process.platform) - const executablePath = ( - capabilities[vendorCapKey]?.binary || - browserFinderMethod()[0] - ) - - const puppeteerOptions: PuppeteerLaunchOptions = Object.assign({ - product, - executablePath, - ignoreDefaultArgs, - headless: Boolean(headless), - defaultViewport: { - width: DEFAULT_WIDTH, - height: DEFAULT_HEIGHT - }, - prefs: capabilities[vendorCapKey]?.prefs, - args: [ - // Set args to honor both ignoreHTTPSErrors and acceptInsecureCerts - // Only for WebKit/Blink engines, Firefox uses a different option - ...['--ignore-certificate-errors'] - .filter(() => - browserType === 'edge' - && (devtoolsOptions.ignoreHTTPSErrors || capabilities.acceptInsecureCerts) ) - ] - }, capabilities[vendorCapKey] || {}, devtoolsOptions || {}) - - if (!executablePath) { - throw new Error('Couldn\'t find executable for browser') - } - - log.info(`Launch ${executablePath} with config: ${JSON.stringify(puppeteerOptions)}`) - return puppeteer.launch(puppeteerOptions) as unknown as Promise -} - -function connectBrowser (connectionUrl: string, capabilities: WebdriverIO.Capabilities) { - const connectionProp = connectionUrl.startsWith('http') ? 'browserURL' : 'browserWSEndpoint' - const devtoolsOptions = capabilities['wdio:devtoolsOptions'] - const options: ConnectOptions = { - [connectionProp]: connectionUrl, - ...devtoolsOptions - } - return puppeteer.connect(options) as unknown as Promise -} - -export default async function launch (capabilities: WebdriverIO.Capabilities) { - try { - Puppeteer.unregisterCustomQueryHandler('shadow') - } catch { - // ignore - } - - // ToDo(Christian): fix types (https://github.com/Georgegriff/query-selector-shadow-dom/issues/77) - Puppeteer.registerCustomQueryHandler('shadow', QueryHandler as any) - const browserName = capabilities.browserName?.toLowerCase() - - /** - * check if capabilities already contains connection details and connect - * to that rather than starting a new browser - */ - const browserOptions = capabilities['goog:chromeOptions'] || capabilities['ms:edgeOptions'] - const devtoolsOptions = capabilities['wdio:devtoolsOptions'] || {} - const connectionUrl = ( - (browserOptions?.debuggerAddress && `http://${browserOptions?.debuggerAddress}`) || - devtoolsOptions.browserURL || - devtoolsOptions.browserWSEndpoint - ) - if (connectionUrl) { - return connectBrowser(connectionUrl, capabilities) - } - - /** - * This fixes running e2e tests on Windows. For some reason within a Vitest environment - * capitalization matters for environment variables. - */ - if (!process.env.PROGRAMFILES && process.env.ProgramFiles) { - process.env.PROGRAMFILES = process.env.ProgramFiles - } - const programFiles86 = process.env['ProgramFiles(X86)'] || process.env['ProgramFiles(x86)'] - if (!process.env['PROGRAMFILES(X86)'] && programFiles86) { - process.env['PROGRAMFILES(X86)'] = programFiles86 - } - - if (browserName && CHROME_NAMES.includes(browserName)) { - return launchChrome(capabilities) - } - - if (browserName && FIREFOX_NAMES.includes(browserName)) { - return launchBrowser(capabilities, BROWSER_TYPE.firefox) - } - - /* istanbul ignore next */ - if (browserName && EDGE_NAMES.includes(browserName)) { - return launchBrowser(capabilities, BROWSER_TYPE.edge) - } - - throw new Error(`Couldn't identify browserName "${browserName}"`) -} diff --git a/packages/devtools/src/scripts/.eslintrc.mjs b/packages/devtools/src/scripts/.eslintrc.mjs deleted file mode 100644 index bb352f4f1..000000000 --- a/packages/devtools/src/scripts/.eslintrc.mjs +++ /dev/null @@ -1,5 +0,0 @@ -module.exports = { - env: { - browser: true - } -} diff --git a/packages/devtools/src/scripts/cleanUpSerializationSelector.ts b/packages/devtools/src/scripts/cleanUpSerializationSelector.ts deleted file mode 100644 index eac0dc05e..000000000 --- a/packages/devtools/src/scripts/cleanUpSerializationSelector.ts +++ /dev/null @@ -1,10 +0,0 @@ -/** - * cleans up temporary attribute that is used to query element - * with puppeteer - */ -export default function cleanUp (elem: Element | Element[], dataProperty: string) { - const elems = Array.isArray(elem) ? elem : [elem] - for (const el of elems) { - el.removeAttribute(dataProperty) - } -} diff --git a/packages/devtools/src/scripts/createWindow.ts b/packages/devtools/src/scripts/createWindow.ts deleted file mode 100644 index 8d62dc01a..000000000 --- a/packages/devtools/src/scripts/createWindow.ts +++ /dev/null @@ -1,3 +0,0 @@ -export default function createWindow (url: string, features: string) { - return window.open(url, '_blank', features) -} diff --git a/packages/devtools/src/scripts/elementClear.ts b/packages/devtools/src/scripts/elementClear.ts deleted file mode 100644 index 75235cb71..000000000 --- a/packages/devtools/src/scripts/elementClear.ts +++ /dev/null @@ -1,6 +0,0 @@ -/** - * clear input value of element - */ -export default function elementClear (html: HTMLElement, elem: HTMLInputElement) { - elem.value = '' -} diff --git a/packages/devtools/src/scripts/executeAsyncScript.ts b/packages/devtools/src/scripts/executeAsyncScript.ts deleted file mode 100644 index a2020cde9..000000000 --- a/packages/devtools/src/scripts/executeAsyncScript.ts +++ /dev/null @@ -1,52 +0,0 @@ -/** - * injector script for executeScript command - * According to the WebDriver spec we need to ensure to serialize all elements - * being returned from the script. In order to do so we attach a data attribute - * to them and fetch them using Puppeteer after the script execution. Instead - * of the element we return the dataFlag string instead to signalize that this - * is suppose to be an element handle - * - * @param {HTMLElement} _ $eval fetched element - * @param {string} script user script - * @param {string} dataProperty property name for elements being returned - * @param {string} dataFlag flag for element - * @param {Object[]} args user arguments for custom script - * @return {Object} result of custom script - */ -export default ( - _: Element, - script: string, - scriptTimeout: number, - dataProperty: string, - dataFlag: string, - ...commandArgs: any[] -): Promise => { - return new Promise((_resolve, _reject) => { - setTimeout( - () => _reject(new Error('Evaluation failed: script timeout')), - scriptTimeout - ) - - ;(window as any).arguments = [...commandArgs, (result: NodeList | any) => { - let tmpResult: (Node | any)[] = result instanceof NodeList ? Array.from(result) : result - const isResultArray = Array.isArray(tmpResult) - tmpResult = isResultArray ? tmpResult : [tmpResult as unknown as Node] - - if (tmpResult.find((r) => r instanceof HTMLElement)) { - tmpResult = tmpResult.map((r, i) => { - if (r instanceof HTMLElement) { - const dataPropertyValue = `${dataFlag}_${i}` - r.setAttribute(dataProperty, dataPropertyValue) - return dataPropertyValue - } - - return r - }) - } - - return _resolve(isResultArray ? tmpResult : tmpResult[0]) - }] - - return eval(script) - }) -} diff --git a/packages/devtools/src/scripts/executeScript.ts b/packages/devtools/src/scripts/executeScript.ts deleted file mode 100644 index 9cc58cd01..000000000 --- a/packages/devtools/src/scripts/executeScript.ts +++ /dev/null @@ -1,36 +0,0 @@ -/** - * injector script for executeScript command - * According to the WebDriver spec we need to ensure to serialize all elements - * being returned from the script. In order to do so we attach a data attribute - * to them and fetch them using Puppeteer after the script execution. Instead - * of the element we return the dataFlag string instead to signalize that this - * is suppose to be an element handle - * - * @param {HTMLElement} _ $eval fetched element - * @param {string} script user script - * @param {string} dataProperty property name for elements being returned - * @param {string} dataFlag flag for element - * @param {Object[]} args user arguments for custom script - * @return {Object} result of custom script - */ -export default (_: HTMLElement, script: string, dataProperty: string, dataFlag: string, ...args: any[]) => { - (window as any).arguments = args - const result = eval(script) - let tmpResult = result instanceof NodeList ? Array.from(result) : result - const isResultArray = Array.isArray(tmpResult) - tmpResult = isResultArray ? tmpResult : [tmpResult] - - if (tmpResult.find((r: any) => r instanceof HTMLElement)) { - tmpResult = tmpResult.map((r: any, i: number) => { - if (r instanceof HTMLElement) { - const dataPropertyValue = `${dataFlag}_${i}` - r.setAttribute(dataProperty, dataPropertyValue) - return dataPropertyValue - } - - return r - }) - } - - return isResultArray ? tmpResult : tmpResult[0] -} diff --git a/packages/devtools/src/scripts/getActiveElement.ts b/packages/devtools/src/scripts/getActiveElement.ts deleted file mode 100644 index 0fdb50ae2..000000000 --- a/packages/devtools/src/scripts/getActiveElement.ts +++ /dev/null @@ -1,11 +0,0 @@ -/** - * returns active element of document - */ -export default function getActiveElement (_: Element, dataProperty: string) { - if (!document.activeElement) { - return false - } - - document.activeElement.setAttribute(dataProperty, 'true') - return true -} diff --git a/packages/devtools/src/scripts/getElementAttribute.ts b/packages/devtools/src/scripts/getElementAttribute.ts deleted file mode 100644 index 675d8a05a..000000000 --- a/packages/devtools/src/scripts/getElementAttribute.ts +++ /dev/null @@ -1,3 +0,0 @@ -export default function getElementAttribute (_: HTMLElement, elem: HTMLElement, attr: string) { - return elem.getAttribute(attr) -} diff --git a/packages/devtools/src/scripts/getElementCSSValue.ts b/packages/devtools/src/scripts/getElementCSSValue.ts deleted file mode 100644 index ad61c3182..000000000 --- a/packages/devtools/src/scripts/getElementCSSValue.ts +++ /dev/null @@ -1,6 +0,0 @@ -export default function getElementCSSValue (_: HTMLElement, elem: HTMLElement, propertyName: string): string { - /** - * Have to cast to any due to https://github.com/Microsoft/TypeScript/issues/17827 - */ - return (window.getComputedStyle(elem) as any)[propertyName] -} diff --git a/packages/devtools/src/scripts/getElementRect.ts b/packages/devtools/src/scripts/getElementRect.ts deleted file mode 100644 index 6ae775c3e..000000000 --- a/packages/devtools/src/scripts/getElementRect.ts +++ /dev/null @@ -1,7 +0,0 @@ -/** - * get element rect - */ -export default function getElementRect (html: HTMLElement, elem: HTMLElement) { - const { x, y, width, height } = elem.getBoundingClientRect() - return { x, y, width, height } -} diff --git a/packages/devtools/src/scripts/getElementTagName.ts b/packages/devtools/src/scripts/getElementTagName.ts deleted file mode 100644 index 3adc3f8a9..000000000 --- a/packages/devtools/src/scripts/getElementTagName.ts +++ /dev/null @@ -1,3 +0,0 @@ -export default function getElementTagName (html: HTMLElement, elem: HTMLElement) { - return elem.tagName -} diff --git a/packages/devtools/src/scripts/getElementText.ts b/packages/devtools/src/scripts/getElementText.ts deleted file mode 100644 index c856ceea2..000000000 --- a/packages/devtools/src/scripts/getElementText.ts +++ /dev/null @@ -1,3 +0,0 @@ -export default function getElementText (html: HTMLElement, elem: HTMLElement) { - return elem.innerText -} diff --git a/packages/devtools/src/scripts/getUrl.ts b/packages/devtools/src/scripts/getUrl.ts deleted file mode 100644 index e6f81b0c8..000000000 --- a/packages/devtools/src/scripts/getUrl.ts +++ /dev/null @@ -1,3 +0,0 @@ -export default function getUrl () { - return document.location.href -} diff --git a/packages/devtools/src/scripts/selectOption.ts b/packages/devtools/src/scripts/selectOption.ts deleted file mode 100644 index d26461bc4..000000000 --- a/packages/devtools/src/scripts/selectOption.ts +++ /dev/null @@ -1,11 +0,0 @@ -export default function selectOption (html: HTMLElement, elem: HTMLOptionElement) { - elem.selected = true - - let parent = elem.parentElement as HTMLElement - while (parent && parent.tagName.toLowerCase() !== 'select') { - parent = parent.parentElement as HTMLElement - } - - parent.dispatchEvent(new Event('input', { bubbles: true })) - parent.dispatchEvent(new Event('change', { bubbles: true })) -} diff --git a/packages/devtools/src/types.ts b/packages/devtools/src/types.ts deleted file mode 100644 index d3e86fa81..000000000 --- a/packages/devtools/src/types.ts +++ /dev/null @@ -1,61 +0,0 @@ -import type { EventEmitter } from 'node:events' - -import type { Options, Capabilities } from '@wdio/types' -import type { ProtocolCommands } from '@wdio/protocols' -import type { LaunchOptions, BrowserLaunchArgumentOptions, BrowserConnectOptions, ConnectOptions } from 'puppeteer-core' -import type { Browser } from 'puppeteer-core/lib/esm/puppeteer/api/Browser.js' -import type { EventEmitter as PuppeteerEventEmitter } from 'puppeteer-core/lib/esm/puppeteer/common/EventEmitter.js' - -declare global { - namespace WebdriverIO { - interface Capabilities { - 'wdio:devtoolsOptions'?: Partial - } - } -} - -export interface DevToolsOptions extends LaunchOptions, BrowserLaunchArgumentOptions, BrowserConnectOptions, ConnectOptions { - /** - * If you want to start Google Chrome on a custom port - */ - customPort?: number -} - -export interface AttachOptions { - capabilities: { - 'goog:chromeOptions': { - debuggerAddress: string - } - } | { - 'ms:edgeOptions': { - debuggerAddress: string - } - } -} - -export interface BaseClient extends EventEmitter { - // id of WebDriver session - sessionId: string - // assigned capabilities by the browser driver / WebDriver server - capabilities: Capabilities.DesiredCapabilities | Capabilities.W3CCapabilities - // original requested capabilities - requestedCapabilities: Capabilities.DesiredCapabilities | Capabilities.W3CCapabilities - // framework options - options: Options.WebDriver -} - -export interface Client extends BaseClient, ProtocolCommands { - puppeteer: Browser -} - -/** - * Interface keeping together information allowing to remove active listener from emitter. - */ -export interface ActiveListener { - /** Event Emitter object emitting to the handler. */ - emitter: PuppeteerEventEmitter - /** Name of the event the handler is attached to. */ - eventName: string - /** Event function handler, bound to the context of its class instance. */ - boundHandler: (...args: any[]) => any -} diff --git a/packages/devtools/src/utils.ts b/packages/devtools/src/utils.ts deleted file mode 100644 index 3d699d730..000000000 --- a/packages/devtools/src/utils.ts +++ /dev/null @@ -1,414 +0,0 @@ -import fs from 'node:fs/promises' -import util from 'node:util' - -import which from 'which' -import logger from '@wdio/logger' -import { resolve } from 'import-meta-resolve' -import { commandCallStructure, isValidParameter, getArgumentType } from '@wdio/utils' -import { canAccess } from '@wdio/utils/node' -import type { CommandParameters, CommandPathVariables, ElementReference } from '@wdio/protocols' -import { WebDriverProtocol } from '@wdio/protocols' -import { launch as launchChromeBrowser, type Options } from 'chrome-launcher' -import type { Logger } from '@wdio/logger' -import type { ElementHandle } from 'puppeteer-core/lib/esm/puppeteer/api/ElementHandle.js' -import type { Browser } from 'puppeteer-core/lib/esm/puppeteer/api/Browser.js' -import type { Frame } from 'puppeteer-core/lib/esm/puppeteer/api/Frame.js' -import type { Page } from 'puppeteer-core/lib/esm/puppeteer/api/Page.js' - -import cleanUp from './scripts/cleanUpSerializationSelector.js' -import { ELEMENT_KEY, SERIALIZE_PROPERTY, SERIALIZE_FLAG, ERROR_MESSAGES } from './constants.js' -import type { Priorities } from './finder/firefox.js' -import type DevToolsDriver from './devtoolsdriver.js' - -const log = logger('devtools') - -export const validate = function ( - command: string, - parameters: CommandParameters[], - variables: CommandPathVariables[], - ref: string, - args: any[] -) { - const commandParams = [...variables.map((v) => Object.assign(v, { - /** - * url variables are: - */ - required: true, // always required as they are part of the endpoint - type: 'string' // have to be always type of string - })), ...parameters] - - const commandUsage = `${command}(${commandParams.map((p) => p.name).join(', ')})` - const moreInfo = `\n\nFor more info see ${ref}\n` - const body: Record = {} - - /** - * parameter check - */ - const minAllowedParams = commandParams.filter((param) => param.required).length - if (args.length < minAllowedParams || args.length > commandParams.length) { - const parameterDescription = commandParams.length - ? `\n\nProperty Description:\n${commandParams.map((p) => ` "${p.name}" (${p.type}): ${p.description}`).join('\n')}` - : '' - - throw new Error( - `Wrong parameters applied for ${command}\n` + - `Usage: ${commandUsage}` + - parameterDescription + - moreInfo - ) - } - - /** - * parameter type check - */ - for (const [i, arg] of Object.entries(args)) { - const commandParam = commandParams[parseInt(i, 10)] - - if (!isValidParameter(arg, commandParam.type)) { - /** - * ignore if argument is not required - */ - if (typeof arg === 'undefined' && !commandParam.required) { - continue - } - - throw new Error( - `Malformed type for "${commandParam.name}" parameter of command ${command}\n` + - `Expected: ${commandParam.type}\n` + - `Actual: ${getArgumentType(arg)}` + - moreInfo - ) - } - - /** - * rest of args are part of body payload - */ - body[commandParams[parseInt(i, 10)].name] = arg - } - - log.info('COMMAND', commandCallStructure(command, args)) - return body -} - -export function getPrototype (commandWrapper: Function) { - const prototype: Record = {} - - for (const [endpoint, methods] of Object.entries(WebDriverProtocol)) { - for (const [method, commandData] of Object.entries(methods)) { - prototype[commandData.command] = { value: commandWrapper(method, endpoint, commandData) } - } - } - - return prototype -} - -export async function findElement ( - this: DevToolsDriver, - context: Frame | Page | ElementHandle, - using: string, - value: string -): Promise { - /** - * implicitly wait for the element if timeout is set - */ - const implicitTimeout = this.timeouts.get('implicit') - using === 'xpath' - ? await context.waitForSelector(`xpath/.${value}`, { timeout: implicitTimeout }) - : await context.waitForSelector(value, { timeout: implicitTimeout }) - - let element: ElementHandle | null = null - try { - element = using === 'xpath' - ? (await context.$$(`xpath/.${value}`))[0] as ElementHandle - : await context.$(value) - } catch (err: any) { - /** - * throw if method failed for other reasons - */ - if (!err.message.includes('failed to find element')) { - throw err - } - } - - /** - * if an element is not found we only return an error to allow - * refetch it at a later stage - */ - if (!element) { - return new Error(`Element with selector "${value}" not found`) - } - - const elementId = this.elementStore.set(element) - return { [ELEMENT_KEY]: elementId } -} - -export async function findElements ( - this: DevToolsDriver, context: Page | Frame | ElementHandle, - using: string, - value: string -): Promise { - /** - * implicitly wait for the element if timeout is set - */ - const implicitTimeout = this.timeouts.get('implicit') - using === 'xpath' - ? await context.waitForSelector(`xpath/.${value}`, { timeout: implicitTimeout }) - : await context.waitForSelector(value, { timeout: implicitTimeout }) - - const elements = using === 'xpath' - ? await context.$$(`xpath/.${value}`) as ElementHandle[] - : await context.$$(value) - - if (elements.length === 0) { - return [] - } - - return elements.map((element) => ({ - [ELEMENT_KEY]: this.elementStore.set(element) - })) -} - -/** - * convert DevTools errors into WebDriver errors so tools upstream - * can handle it in similar fashion (e.g. stale element) - */ -export function sanitizeError (err: Error) { - let errorMessage = err.message - - if (err.message.includes('Node is detached from document')) { - err.name = ERROR_MESSAGES.staleElement.name - errorMessage = ERROR_MESSAGES.staleElement.message - } - - const stack = err.stack ? err.stack.split('\n') : [] - const asyncStack = stack.lastIndexOf(' -- ASYNC --') - err.stack = errorMessage + '\n' + stack.slice(asyncStack + 1) - .filter((line) => !line.includes('devtools/node_modules/puppeteer-core')) - .join('\n') - return err -} - -/** - * transform elements in argument list to Puppeteer element handles - */ -export async function transformExecuteArgs (this: DevToolsDriver, args: any[] = []): Promise { - return Promise.all(args.map(async (arg) => { - if (arg && arg[ELEMENT_KEY]) { - const elementHandle = await this.elementStore.get(arg[ELEMENT_KEY]) - - if (!elementHandle) { - throw getStaleElementError(arg[ELEMENT_KEY]) - } - - arg = elementHandle - } - - return arg - })) -} - -/** - * fetch marked elements from execute script - */ -export async function transformExecuteResult ( - this: DevToolsDriver, - page: Page, - result: any | any[] -) { - const isResultArray = Array.isArray(result) - let tmpResult = isResultArray ? result : [result] - - if (tmpResult.find((r: any) => typeof r === 'string' && r.startsWith(SERIALIZE_FLAG))) { - tmpResult = await Promise.all(tmpResult.map(async (r: any) => { - if (typeof r === 'string' && r.startsWith(SERIALIZE_FLAG)) { - return findElement.call(this, page, 'css selector', `[${SERIALIZE_PROPERTY}="${r}"]`) - } - - return r - })) - - await page.$$eval(`[${SERIALIZE_PROPERTY}]`, cleanUp, SERIALIZE_PROPERTY) - } - - return isResultArray ? tmpResult : tmpResult[0] -} - -export function getStaleElementError (elementId: string) { - const error = new Error( - `stale element reference: The element with reference ${elementId} is stale; either the ` + - 'element is no longer attached to the DOM, it is not in the current frame context, or the ' + - 'document has been refreshed' - ) - error.name = 'stale element reference' - return error -} - -/** - * Helper function to get a list of Puppeteer pages from a Chrome browser. - * In case many headless browser are run in parallel there are situations - * where there are no pages because the machine is busy booting the headless - * browser. - * - * @param {Puppeteer.Browser} browser browser instance - * @return {Puppeteer.Page[]} list of browser pages - */ -export async function getPages (browser: Browser, retryInterval = 100): Promise { - const pages = await browser.pages() - - if (pages.length === 0) { - log.info('no browser pages found, retrying...') - - /** - * wait for some milliseconds to try again - */ - await new Promise((resolve) => setTimeout(resolve, retryInterval)) - return getPages(browser) - } - - return pages -} - -export function sort(installations: string[], priorities: Priorities[]) { - const defaultPriority = 10 - return installations - // assign priorities - .map((inst) => { - for (const pair of priorities) { - if (pair.regex.test(inst)) { - return { path: inst, weight: pair.weight } - } - } - - return { path: inst, weight: defaultPriority } - }) - // sort based on priorities - .sort((a, b) => (b.weight - a.weight)) - // remove priority flag - .map(pair => pair.path) -} - -/** - * helper utility to clone a list - * @param {Any[]} arr list of things - * @return {Any[]} new list of same things - */ -export function uniq(arr: string[]) { - return Array.from(new Set(arr)) -} - -/** - * Look for edge executables by using the which command - */ -export function findByWhich (executables: string[], priorities: Priorities[]) { - const installations: string[] = [] - executables.forEach((executable) => { - try { - const browserPath = which.sync(executable) - if (canAccess(browserPath)) { - installations.push(browserPath) - } - } catch (err: any) { - // Not installed. - } - }) - - return sort(uniq(installations.filter(Boolean)), priorities) -} - -/** - * monkey patch debug package to log CDP messages from Puppeteer - */ -export async function patchDebug (scoppedLogger: Logger) { - /** - * let's not get caught by our dep checker, therefore - * define package name in variable first - */ - const pkgName = 'debug' - - try { - /** - * log puppeteer messages - * resolve debug *from* puppeteer-core to make sure we monkey patch the version - * it will use - */ - const puppeteerPkg = await resolve('puppeteer-core', import.meta.url) - let puppeteerDebugPkg = await resolve(pkgName, puppeteerPkg) - - /** - * check if Puppeteer has its own version of debug, if not use the - * one that is installed for all packages - */ - if (!await fs.access(puppeteerDebugPkg).then(() => true, () => false)) { - /** - * let's not get caught by our dep checker, therefor - * define package name in variable first - */ - puppeteerDebugPkg = await resolve(pkgName, import.meta.url) - } - - const debug = (await import(puppeteerDebugPkg)).default - debug.log = (msg: string) => { - if (msg.includes('puppeteer:protocol')) { - msg = msg.split('\n') - .map((l) => l.slice(Math.max(l.indexOf('◀ '), l.indexOf('► '))) - .replace('\x1B[32m', '') - .replace('\x1B[39m', '') - .replace('◀ \x1B[0m', '') - .replace('► \x1B[0m', '') - .trim() - ) - .join('') - // remove [' and '] - .slice(2, -2) - } - scoppedLogger.debug(msg) - } - } catch (err) { - log.warn('Couldn\'t stub Puppeteer debug package, Puppeteer logs might get lost') - } -} - -export const sleep = (time = 0) => new Promise( - (resolve) => setTimeout(resolve, time)) - -export const launchChromeUsingWhich = async (err: Error, launchOptions: Options, ) => { - /** - * we only handle the error if - */ - if ( - /** - * the installation could not be found - */ - !err.message.includes('No Chrome installations found.') || - /** - * the user specified a binary path that could not be found - */ - typeof launchOptions.chromePath === 'string' - ) { - throw err - } - - /** - * try to use node-which to resolve a Chrome path - */ - const errorMessage = ( - 'Failed to find a Chrome installation via:\n' + - '$ which chrome: %s\n' + - '$ which chromium: %s\n' + - '$ which google-chrome: %s' - ) - const chromePath = await which('chrome') - .catch((errorChrome: Error) => which('chromium') - .catch((errorChromium: Error) => which('google-chrome') - .catch((errorGoogleChrome: Error) => { - throw new Error(util.format( - errorMessage, - errorChrome.message, - errorChromium.message, - errorGoogleChrome.message - )) - }) - ) - ) - return launchChromeBrowser({ ...launchOptions, chromePath }) -} diff --git a/packages/devtools/tests/__snapshots__/devtools.test.ts.snap b/packages/devtools/tests/__snapshots__/devtools.test.ts.snap deleted file mode 100644 index 75848093a..000000000 --- a/packages/devtools/tests/__snapshots__/devtools.test.ts.snap +++ /dev/null @@ -1,33 +0,0 @@ -// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html - -exports[`attachSession 1`] = ` -{ - "browserName": "chrome", - "browserVersion": "80.0.3987.149", - "goog:chromeOptions": { - "debuggerAddress": "localhost:49375", - }, -} -`; - -exports[`attachSession 2`] = ` -{ - "browserName": "chrome", -} -`; - -exports[`newSession 1`] = ` -{ - "browserName": "chrome", - "browserVersion": "80.0.3987.149", - "goog:chromeOptions": { - "debuggerAddress": "localhost:49375", - }, -} -`; - -exports[`newSession 2`] = ` -{ - "browserName": "chrome", -} -`; diff --git a/packages/devtools/tests/__snapshots__/devtoolsdriver.test.ts.snap b/packages/devtools/tests/__snapshots__/devtoolsdriver.test.ts.snap deleted file mode 100644 index 5b9ed7c80..000000000 --- a/packages/devtools/tests/__snapshots__/devtoolsdriver.test.ts.snap +++ /dev/null @@ -1,164 +0,0 @@ -// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html - -exports[`setTimeouts with all timeouts 1`] = ` -Map { - "implicit" => 222, - "pageLoad" => 333, - "script" => 444, -} -`; - -exports[`setTimeouts with implicit and pageLoad timeout 1`] = ` -Map { - "implicit" => 222, - "pageLoad" => 333, - "script" => 30000, -} -`; - -exports[`setTimeouts with implicit timeout 1`] = ` -Map { - "implicit" => 222, - "pageLoad" => 100, - "script" => 30000, -} -`; - -exports[`should rerun command 3 times before attempting to wait for page load 1`] = ` -[ - [ - "command", - { - "command": "elementClick", - "params": { - "elementId": "123", - "text": "some text", - "value": [ - "some value", - ], - }, - "retries": 0, - }, - ], - [ - "command", - { - "command": "elementClick", - "params": { - "elementId": "123", - "text": "some text", - "value": [ - "some value", - ], - }, - "retries": 1, - }, - ], - [ - "command", - { - "command": "elementClick", - "params": { - "elementId": "123", - "text": "some text", - "value": [ - "some value", - ], - }, - "retries": 2, - }, - ], -] -`; - -exports[`should rerun command if it was executed within navigation 1`] = ` -[ - [ - "command", - { - "command": "elementClick", - "params": { - "elementId": "123", - "text": "some text", - "value": [ - "some value", - ], - }, - "retries": 0, - }, - ], - [ - "command", - { - "command": "elementClick", - "params": { - "elementId": "123", - "text": "some text", - "value": [ - "some value", - ], - }, - "retries": 1, - }, - ], - [ - "result", - { - "command": "elementClick", - "params": { - "elementId": "123", - "text": "some text", - "value": [ - "some value", - ], - }, - "result": { - "value": null, - }, - "retries": 1, - }, - ], -] -`; - -exports[`should return proper result 1`] = ` -[ - [ - "command", - { - "command": "elementClick", - "params": { - "elementId": "123", - "text": "some text", - "value": [ - "some value", - ], - }, - "retries": 0, - }, - ], - [ - "result", - { - "command": "elementClick", - "params": { - "elementId": "123", - "text": "some text", - "value": [ - "some value", - ], - }, - "result": { - "value": { - "elementId": "123", - "text": "some text", - "value": [ - "some value", - ], - }, - }, - "retries": 0, - }, - ], -] -`; diff --git a/packages/devtools/tests/__snapshots__/launcher.test.ts.snap b/packages/devtools/tests/__snapshots__/launcher.test.ts.snap deleted file mode 100644 index a183694b0..000000000 --- a/packages/devtools/tests/__snapshots__/launcher.test.ts.snap +++ /dev/null @@ -1,508 +0,0 @@ -// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html - -exports[`launch Edge with custom arguments 1`] = ` -[ - [ - { - "args": [ - "foobar", - ], - "defaultViewport": { - "height": 456, - "width": 123, - }, - "executablePath": "/path/to/edge", - "headless": true, - "ignoreDefaultArgs": undefined, - "ignoreHTTPSErrors": false, - "prefs": undefined, - "product": "chrome", - }, - ], -] -`; - -exports[`launch Edge with default values 1`] = ` -[ - [ - { - "args": [], - "defaultViewport": { - "height": 900, - "width": 1200, - }, - "executablePath": "/path/to/edge", - "headless": false, - "ignoreDefaultArgs": undefined, - "ignoreHTTPSErrors": false, - "prefs": undefined, - "product": "chrome", - }, - ], -] -`; - -exports[`launch Edge without Puppeteer default args (backwards compat) 1`] = ` -[ - [ - { - "args": [], - "defaultViewport": { - "height": 900, - "width": 1200, - }, - "executablePath": "/path/to/edge", - "headless": true, - "ignoreDefaultArgs": true, - "ignoreHTTPSErrors": false, - "prefs": { - "foo": "bar", - }, - "product": "chrome", - }, - ], -] -`; - -exports[`launch Edge without Puppeteer default args 1`] = ` -[ - [ - { - "args": [], - "defaultViewport": { - "height": 900, - "width": 1200, - }, - "executablePath": "/path/to/edge", - "headless": true, - "ignoreDefaultArgs": true, - "ignoreHTTPSErrors": false, - "prefs": undefined, - "product": "chrome", - }, - ], -] -`; - -exports[`launch Firefox Nightly binary without Puppeteer default args 1`] = ` -[ - [ - { - "args": [], - "binary": "firefox", - "defaultViewport": { - "height": 900, - "width": 1200, - }, - "executablePath": "firefox", - "headless": true, - "ignoreDefaultArgs": true, - "ignoreHTTPSErrors": false, - "prefs": { - "foo": "bar", - }, - "product": "firefox", - }, - ], -] -`; - -exports[`launch Firefox Nightly without Puppeteer default args 1`] = ` -[ - [ - { - "args": [], - "binary": "/foo/firefox-nightly", - "defaultViewport": { - "height": 900, - "width": 1200, - }, - "executablePath": "/foo/firefox-nightly", - "headless": true, - "ignoreDefaultArgs": true, - "ignoreHTTPSErrors": false, - "prefs": undefined, - "product": "firefox", - }, - ], -] -`; - -exports[`launch Firefox with custom arguments 1`] = ` -[ - [ - { - "args": [], - "defaultViewport": { - "height": 456, - "width": 123, - }, - "executablePath": "/path/to/firefox", - "headless": true, - "ignoreDefaultArgs": undefined, - "ignoreHTTPSErrors": false, - "prefs": undefined, - "product": "firefox", - }, - ], -] -`; - -exports[`launch chrome with chrome arguments 1`] = ` -[ - [ - { - "chromeFlags": [ - "--enable-automation", - "--disable-popup-blocking", - "--disable-extensions", - "--disable-background-networking", - "--disable-background-timer-throttling", - "--disable-backgrounding-occluded-windows", - "--disable-sync", - "--metrics-recording-only", - "--disable-default-apps", - "--mute-audio", - "--no-first-run", - "--no-default-browser-check", - "--disable-hang-monitor", - "--disable-prompt-on-repost", - "--disable-client-side-phishing-detection", - "--password-store=basic", - "--use-mock-keychain", - "--disable-component-extensions-with-background-pages", - "--disable-breakpad", - "--disable-dev-shm-usage", - "--disable-ipc-flooding-protection", - "--disable-renderer-backgrounding", - "--force-fieldtrials=*BackgroundTracing/default/", - "--enable-features=NetworkService,NetworkServiceInProcess", - "--disable-features=site-per-process,TranslateUI,BlinkGenPropertyTrees", - "--window-position=0,0", - "--window-size=412,732", - "--headless", - "--no-sandbox", - "--window-size=222,333", - "--device-scale-factor=3.5", - "--user-agent=Mozilla/5.0 (Linux; Android 8.0.0; Nexus 6P Build/OPP3.170518.006) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3765.0 Mobile Safari/537.36", - ], - "chromePath": "/foo/bar", - "envVars": { - "foo": "bar", - }, - "ignoreDefaultFlags": true, - "prefs": undefined, - "userDataDir": undefined, - }, - ], -] -`; - -exports[`launch chrome with chrome port 1`] = ` -[ - [ - { - "chromeFlags": [ - "--enable-automation", - "--disable-popup-blocking", - "--disable-extensions", - "--disable-background-networking", - "--disable-background-timer-throttling", - "--disable-backgrounding-occluded-windows", - "--disable-sync", - "--metrics-recording-only", - "--disable-default-apps", - "--mute-audio", - "--no-first-run", - "--no-default-browser-check", - "--disable-hang-monitor", - "--disable-prompt-on-repost", - "--disable-client-side-phishing-detection", - "--password-store=basic", - "--use-mock-keychain", - "--disable-component-extensions-with-background-pages", - "--disable-breakpad", - "--disable-dev-shm-usage", - "--disable-ipc-flooding-protection", - "--disable-renderer-backgrounding", - "--force-fieldtrials=*BackgroundTracing/default/", - "--enable-features=NetworkService,NetworkServiceInProcess", - "--disable-features=site-per-process,TranslateUI,BlinkGenPropertyTrees", - "--window-position=0,0", - "--window-size=1200,900", - ], - "chromePath": undefined, - "envVars": undefined, - "ignoreDefaultFlags": true, - "port": 8041, - "prefs": undefined, - "userDataDir": undefined, - }, - ], -] -`; - -exports[`launch chrome with custom user data dir 1`] = ` -[ - [ - { - "chromeFlags": [ - "--enable-automation", - "--disable-popup-blocking", - "--disable-extensions", - "--disable-background-networking", - "--disable-background-timer-throttling", - "--disable-backgrounding-occluded-windows", - "--disable-sync", - "--metrics-recording-only", - "--disable-default-apps", - "--mute-audio", - "--no-first-run", - "--no-default-browser-check", - "--disable-hang-monitor", - "--disable-prompt-on-repost", - "--disable-client-side-phishing-detection", - "--password-store=basic", - "--use-mock-keychain", - "--disable-component-extensions-with-background-pages", - "--disable-breakpad", - "--disable-dev-shm-usage", - "--disable-ipc-flooding-protection", - "--disable-renderer-backgrounding", - "--force-fieldtrials=*BackgroundTracing/default/", - "--enable-features=NetworkService,NetworkServiceInProcess", - "--disable-features=site-per-process,TranslateUI,BlinkGenPropertyTrees", - "--window-position=0,0", - "--window-size=1200,900", - "--enable-features=NetworkService", - "--someOtherFlag", - "--anotherFlag", - ], - "chromePath": undefined, - "envVars": undefined, - "ignoreDefaultFlags": true, - "prefs": undefined, - "userDataDir": "/foo/bar", - }, - ], -] -`; - -exports[`launch chrome with default values 1`] = ` -[ - [ - { - "chromeFlags": [ - "--enable-automation", - "--disable-popup-blocking", - "--disable-extensions", - "--disable-background-networking", - "--disable-background-timer-throttling", - "--disable-backgrounding-occluded-windows", - "--disable-sync", - "--metrics-recording-only", - "--disable-default-apps", - "--mute-audio", - "--no-first-run", - "--no-default-browser-check", - "--disable-hang-monitor", - "--disable-prompt-on-repost", - "--disable-client-side-phishing-detection", - "--password-store=basic", - "--use-mock-keychain", - "--disable-component-extensions-with-background-pages", - "--disable-breakpad", - "--disable-dev-shm-usage", - "--disable-ipc-flooding-protection", - "--disable-renderer-backgrounding", - "--force-fieldtrials=*BackgroundTracing/default/", - "--enable-features=NetworkService,NetworkServiceInProcess", - "--disable-features=site-per-process,TranslateUI,BlinkGenPropertyTrees", - "--window-position=0,0", - "--window-size=1200,900", - ], - "chromePath": undefined, - "envVars": undefined, - "ignoreDefaultFlags": true, - "prefs": undefined, - "userDataDir": undefined, - }, - ], -] -`; - -exports[`launch chrome with default values 2`] = ` -[ - [ - { - "browserURL": "http://127.0.0.1:1234", - "defaultViewport": null, - }, - ], -] -`; - -exports[`launch chrome with defaultViewport in wdio:devtoolsOptions 1`] = ` -[ - [ - { - "chromeFlags": [ - "--enable-automation", - "--disable-popup-blocking", - "--disable-extensions", - "--disable-background-networking", - "--disable-background-timer-throttling", - "--disable-backgrounding-occluded-windows", - "--disable-sync", - "--metrics-recording-only", - "--disable-default-apps", - "--mute-audio", - "--no-first-run", - "--no-default-browser-check", - "--disable-hang-monitor", - "--disable-prompt-on-repost", - "--disable-client-side-phishing-detection", - "--password-store=basic", - "--use-mock-keychain", - "--disable-component-extensions-with-background-pages", - "--disable-breakpad", - "--disable-dev-shm-usage", - "--disable-ipc-flooding-protection", - "--disable-renderer-backgrounding", - "--force-fieldtrials=*BackgroundTracing/default/", - "--enable-features=NetworkService,NetworkServiceInProcess", - "--disable-features=site-per-process,TranslateUI,BlinkGenPropertyTrees", - "--window-position=0,0", - "--window-size=222,333", - "--device-scale-factor=1.42", - "--enable-touch-drag-drop", - "--touch-events", - "--enable-viewport", - ], - "chromePath": undefined, - "envVars": undefined, - "ignoreDefaultFlags": true, - "prefs": undefined, - "userDataDir": undefined, - }, - ], -] -`; - -exports[`launch chrome without default flags and without puppeteer default args 1`] = ` -[ - [ - { - "chromeFlags": [ - "--window-position=0,0", - "--window-size=1200,900", - ], - "chromePath": undefined, - "envVars": undefined, - "ignoreDefaultFlags": true, - "prefs": undefined, - "userDataDir": undefined, - }, - ], -] -`; - -exports[`launch chrome without default flags and without puppeteer default args 2`] = ` -[ - [ - { - "browserURL": "http://127.0.0.1:1234", - "defaultViewport": null, - "ignoreDefaultArgs": true, - }, - ], -] -`; - -exports[`overriding chrome default flags (backwards compat) 1`] = ` -[ - [ - { - "chromeFlags": [ - "--enable-automation", - "--disable-popup-blocking", - "--disable-extensions", - "--disable-background-networking", - "--disable-background-timer-throttling", - "--disable-backgrounding-occluded-windows", - "--metrics-recording-only", - "--disable-default-apps", - "--mute-audio", - "--no-first-run", - "--no-default-browser-check", - "--disable-hang-monitor", - "--disable-prompt-on-repost", - "--disable-client-side-phishing-detection", - "--password-store=basic", - "--use-mock-keychain", - "--disable-component-extensions-with-background-pages", - "--disable-breakpad", - "--disable-dev-shm-usage", - "--disable-ipc-flooding-protection", - "--disable-renderer-backgrounding", - "--force-fieldtrials=*BackgroundTracing/default/", - "--disable-features=site-per-process,TranslateUI,BlinkGenPropertyTrees", - "--window-position=0,0", - "--window-size=1200,900", - "--headless", - "--no-sandbox", - "--enable-features=NetworkService", - ], - "chromePath": undefined, - "envVars": undefined, - "ignoreDefaultFlags": true, - "prefs": { - "bar": "foo", - }, - "userDataDir": undefined, - }, - ], -] -`; - -exports[`overriding chrome default flags 1`] = ` -[ - [ - { - "chromeFlags": [ - "--enable-automation", - "--disable-popup-blocking", - "--disable-extensions", - "--disable-background-networking", - "--disable-background-timer-throttling", - "--disable-backgrounding-occluded-windows", - "--metrics-recording-only", - "--disable-default-apps", - "--mute-audio", - "--no-first-run", - "--no-default-browser-check", - "--disable-hang-monitor", - "--disable-prompt-on-repost", - "--disable-client-side-phishing-detection", - "--password-store=basic", - "--use-mock-keychain", - "--disable-component-extensions-with-background-pages", - "--disable-breakpad", - "--disable-dev-shm-usage", - "--disable-ipc-flooding-protection", - "--disable-renderer-backgrounding", - "--force-fieldtrials=*BackgroundTracing/default/", - "--disable-features=site-per-process,TranslateUI,BlinkGenPropertyTrees", - "--window-position=0,0", - "--window-size=1200,900", - "--enable-features=NetworkService", - ], - "chromePath": undefined, - "envVars": undefined, - "ignoreDefaultFlags": true, - "prefs": undefined, - "userDataDir": undefined, - }, - ], -] -`; diff --git a/packages/devtools/tests/__snapshots__/utils.test.ts.snap b/packages/devtools/tests/__snapshots__/utils.test.ts.snap deleted file mode 100644 index ed62b1cce..000000000 --- a/packages/devtools/tests/__snapshots__/utils.test.ts.snap +++ /dev/null @@ -1,241 +0,0 @@ -// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html - -exports[`findElement utils > findElements > should find elements 1`] = ` -[ - { - "element-6066-11e4-a52e-4f735466cecf": "foobar", - }, - { - "element-6066-11e4-a52e-4f735466cecf": "foobar", - }, -] -`; - -exports[`findElement utils > findElements > should find elements with xpath 1`] = ` -[ - { - "element-6066-11e4-a52e-4f735466cecf": "foobar", - }, - { - "element-6066-11e4-a52e-4f735466cecf": "foobar", - }, -] -`; - -exports[`getPrototype 1`] = ` -{ - "acceptAlert": { - "value": 56, - }, - "addCookie": { - "value": 49, - }, - "back": { - "value": 8, - }, - "closeWindow": { - "value": 13, - }, - "createWindow": { - "value": 15, - }, - "deleteAllCookies": { - "value": 50, - }, - "deleteCookie": { - "value": 52, - }, - "deleteSession": { - "value": 2, - }, - "dismissAlert": { - "value": 55, - }, - "elementClear": { - "value": 43, - }, - "elementClick": { - "value": 42, - }, - "elementSendKeys": { - "value": 44, - }, - "executeAsyncScript": { - "value": 47, - }, - "executeScript": { - "value": 46, - }, - "findElement": { - "value": 25, - }, - "findElementFromElement": { - "value": 29, - }, - "findElementFromShadowRoot": { - "value": 26, - }, - "findElements": { - "value": 27, - }, - "findElementsFromElement": { - "value": 30, - }, - "findElementsFromShadowRoot": { - "value": 28, - }, - "forward": { - "value": 9, - }, - "fullscreenWindow": { - "value": 24, - }, - "getActiveElement": { - "value": 32, - }, - "getAlertText": { - "value": 57, - }, - "getAllCookies": { - "value": 48, - }, - "getElementAttribute": { - "value": 35, - }, - "getElementCSSValue": { - "value": 37, - }, - "getElementComputedLabel": { - "value": 62, - }, - "getElementComputedRole": { - "value": 61, - }, - "getElementProperty": { - "value": 36, - }, - "getElementRect": { - "value": 40, - }, - "getElementShadowRoot": { - "value": 31, - }, - "getElementTagName": { - "value": 39, - }, - "getElementText": { - "value": 38, - }, - "getNamedCookie": { - "value": 51, - }, - "getPageSource": { - "value": 45, - }, - "getTimeouts": { - "value": 4, - }, - "getTitle": { - "value": 11, - }, - "getUrl": { - "value": 6, - }, - "getWindowHandle": { - "value": 12, - }, - "getWindowHandles": { - "value": 16, - }, - "getWindowRect": { - "value": 20, - }, - "isElementDisplayed": { - "value": 34, - }, - "isElementEnabled": { - "value": 41, - }, - "isElementSelected": { - "value": 33, - }, - "maximizeWindow": { - "value": 22, - }, - "minimizeWindow": { - "value": 23, - }, - "navigateTo": { - "value": 7, - }, - "newSession": { - "value": 1, - }, - "performActions": { - "value": 53, - }, - "printPage": { - "value": 17, - }, - "refresh": { - "value": 10, - }, - "releaseActions": { - "value": 54, - }, - "sendAlertText": { - "value": 58, - }, - "setTimeouts": { - "value": 5, - }, - "setWindowRect": { - "value": 21, - }, - "status": { - "value": 3, - }, - "switchToFrame": { - "value": 18, - }, - "switchToParentFrame": { - "value": 19, - }, - "switchToWindow": { - "value": 14, - }, - "takeElementScreenshot": { - "value": 60, - }, - "takeScreenshot": { - "value": 59, - }, -} -`; - -exports[`transformExecuteResult > multiple results 1`] = ` -[ - "foobar", - { - "element-6066-11e4-a52e-4f735466cecf": "foobar", - }, - 42, -] -`; - -exports[`validate > should do a proper request 1`] = ` -{ - "elementId": "123", - "using": "css selector", - "value": "#body", -} -`; - -exports[`validate > should do a proper request with non required params 1`] = ` -{ - "customParam": 123, - "elementId": "123", - "using": "css selector", - "value": "#body", -} -`; diff --git a/packages/devtools/tests/devtools.test.ts b/packages/devtools/tests/devtools.test.ts deleted file mode 100644 index 60fcbf280..000000000 --- a/packages/devtools/tests/devtools.test.ts +++ /dev/null @@ -1,84 +0,0 @@ -import path from 'node:path' -import { expect, test, vi, beforeEach } from 'vitest' -import type { Capabilities } from '@wdio/types' -import launch from '../src/launcher.js' -import DevTools from '../src/index.js' - -vi.mock('@wdio/logger', () => import(path.join(process.cwd(), '__mocks__', '@wdio/logger'))) -vi.mock('../src/launcher', () => ({ - default: vi.fn().mockImplementation((capabilities) => { - capabilities['goog:chromeOptions'] = capabilities['goog:chromeOptions'] || {} - return { - on: vi.fn(), - off: vi.fn(), - pages: vi.fn().mockReturnValue(Promise.resolve([{ - on: vi.fn(), - off: vi.fn(), - setDefaultTimeout: vi.fn() - }])), - wsEndpoint: vi.fn().mockReturnValue('ws://localhost:49375/devtools/browser/c4b017ea-f476-4026-a699-bc5d4858cfe1'), - userAgent: vi.fn().mockReturnValue(Promise.resolve('Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.149 Safari/537.36')) - } - }) -})) - -beforeEach(() => { - vi.mocked(launch).mockClear() -}) - -test('newSession', async () => { - const client: any = await DevTools.newSession({ - logLevel: 'trace', - capabilities: { - browserName: 'chrome' - } - }) - - /** - * don't include platform specific information in snapshot - */ - delete (client.options.capabilities as Capabilities.DesiredCapabilities).platformName - delete (client.options.capabilities as Capabilities.DesiredCapabilities).platform - - expect(client.options.capabilities).toMatchSnapshot() - expect(client.options.requestedCapabilities).toMatchSnapshot() - expect(client.isDevTools).toBe(true) - expect(client.isChrome).toBe(true) - expect(client.isW3C).toBe(true) - expect(launch).toBeCalledTimes(1) -}) - -test('reloadSession', async () => { - const client = await DevTools.newSession({ - logLevel: 'trace', - capabilities: { - browserName: 'chrome' - } - }) - const origSessionId = client.sessionId - const newClient = await DevTools.reloadSession(client) - expect(origSessionId).toBe(newClient) -}) - -test('attachSession', async () => { - const client: any = await DevTools.newSession({ - logLevel: 'trace', - capabilities: { - browserName: 'chrome' - } - }) - const otherClient: any = await DevTools.attachToSession(client) - - /** - * don't include platform specific information in snapshot - */ - delete (otherClient.options.capabilities as Capabilities.DesiredCapabilities).platformName - delete (otherClient.options.capabilities as Capabilities.DesiredCapabilities).platform - - expect(otherClient.capabilities).toMatchSnapshot() - expect(otherClient.requestedCapabilities).toMatchSnapshot() - expect(otherClient.isDevTools).toBe(true) - expect(otherClient.isChrome).toBe(true) - expect(otherClient.isW3C).toBe(true) - expect(launch).toBeCalledTimes(2) -}) diff --git a/packages/devtools/tests/devtoolsdriver.test.ts b/packages/devtools/tests/devtoolsdriver.test.ts deleted file mode 100644 index 25bc8d40b..000000000 --- a/packages/devtools/tests/devtoolsdriver.test.ts +++ /dev/null @@ -1,378 +0,0 @@ -import path from 'node:path' -import { expect, test, vi, beforeEach } from 'vitest' -import DevToolsDriver from '../src/devtoolsdriver.js' -import type { Dialog } from 'puppeteer-core/lib/esm/puppeteer/common/Dialog.js' -import type { Frame } from 'puppeteer-core/lib/esm/puppeteer/api/Frame.js' - -vi.mock('@wdio/logger', () => import(path.join(process.cwd(), '__mocks__', '@wdio/logger'))) -vi.mock('puppeteer-core', () => import(path.join(process.cwd(), '__mocks__', 'puppeteer-core'))) -vi.mock('chrome-launcher', () => import(path.join(process.cwd(), '__mocks__', 'chrome-launcher'))) - -vi.mock('fs', () => ({ - default: { - existsSync: vi.fn().mockReturnValue(false), - readdirSync: vi.fn().mockReturnValue([ - '/foo/bar/click.js', - '/foo/bar/getAttribute.ts', - '/foo/bar/getAttribute.d.ts' - ]) - } -})) - -vi.mock('../src/commands/index.js', () => ({ - click: 'clickCommand', - getAttribute: 'getAttributeCommand' -})) - -let evaluateCommandCalls = 0 -const executionContext = { - evaluate: vi.fn() -} - -const setNormalPageLoadBehavior = () => executionContext.evaluate.mockImplementation(() => Promise.resolve(++evaluateCommandCalls % 2 === 1 - ? '1' - : 'complete' -)) - -const frame = { - executionContext: vi.fn().mockImplementation(() => Promise.resolve(executionContext)) -} - -const page = { - on: vi.fn(), - once: vi.fn(), - setDefaultTimeout: vi.fn(), - mainFrame: vi.fn(), - frames: vi.fn().mockReturnValue([frame]) -} - -const browser = { - on: vi.fn(), - pages: vi.fn().mockImplementation(async () => { - page.mainFrame = vi.fn().mockImplementation(() => frame) - ++evaluateCommandCalls - setNormalPageLoadBehavior() - return [page] - }) -} - -const commandMock = { - command: 'elementClick', - description: 'foobar', - ref: 'http://foobar.com', - variables: [{ - name: 'elementId', - description: 'barfoo' - }], - parameters: [{ - name: 'text', - type: 'string', - description: 'some description', - required: true - }, { - name: 'value', - type: 'string[]', - description: 'some other description', - required: false - }] -} - -let driver: DevToolsDriver - -beforeEach(() => { - page.on.mockClear() - page.once.mockClear() - page.setDefaultTimeout.mockClear() - page.mainFrame.mockImplementation(() => frame) - executionContext.evaluate.mockClear() - setNormalPageLoadBehavior() - driver = new DevToolsDriver(browser as any, [page as any]) - driver.timeouts.set('pageLoad', 100) -}) - -test('can be initiated', () => { - expect(driver.commands).toEqual({ click: 'clickCommand', getAttribute: 'getAttributeCommand' }) - expect(driver.browser).toEqual(browser) - expect(browser.on).toBeCalledWith('targetcreated', expect.any(Function)) - expect(browser.on).toBeCalledWith('targetdestroyed', expect.any(Function)) - expect(page.on).toBeCalledWith('dialog', expect.any(Function)) - expect(page.on).toBeCalledWith('framenavigated', expect.any(Function)) -}) - -test('should throw if command is called that is not implemented', () => { - const command = driver.register({ - command: 'click', - description: 'some description', - ref: 'https://foobar.com', - parameters: [] - }) - expect(command).toThrow('Command "click" is not supported using the Devtools protocol.') -}) - -test('should return proper result', async () => { - driver.commands.elementClick = (...args: any[]) => new Promise( - (resolve) => setTimeout(() => resolve.apply(null, args), 100)) - - const emit = vi.fn() - const command = driver.register(commandMock) - - const errorMsg = await command.call({ emit } as any).catch((e) => e.message) - await expect(errorMsg).toContain('Wrong parameters applied') - - const errorMsg2 = await command.call(emit as any, '123').catch((e) => e.message) - await expect(errorMsg2).toContain('Wrong parameters applied') - - emit.mockReset() - const errorMsg3 = await command.call({ emit } as any, '123', 'some text', ['some value']) - await expect(errorMsg3).toEqual({ - elementId: '123', - text: 'some text', - value: ['some value'] - }) - expect(emit.mock.calls).toMatchSnapshot() -}) - -test('should throw if command throws', async () => { - driver.commands.elementClick = () => new Promise( - (resolve, reject) => setTimeout(() => reject(new Error('foobar')), 100)) - - const command = driver.register(commandMock) - - const emit = vi.fn() - const errorMsg3 = await command.call({ emit } as any, '123', 'some text', ['some value']) - .catch((e) => e.message) - await expect(errorMsg3).toEqual('foobar') -}) - -test('should rerun command if it was executed within navigation', async () => { - expect.assertions(3) - let wasCommandCalled = false - driver.commands.elementClick = vi.fn().mockImplementation( - () => new Promise((resolve, reject) => setTimeout( - () => { - if (!wasCommandCalled) { - wasCommandCalled = true - reject(new Error('foobar most likely because of a navigation')) - } - - resolve(null) - }, - 100) - ) - ) - - const command = driver.register(commandMock) - - const emit = vi.fn() - - const result = await command.call({ emit } as any, '123', 'some text', ['some value']) - - expect(driver.commands.elementClick).toBeCalledTimes(2) - expect(result).toBe(null) - expect(emit.mock.calls).toMatchSnapshot() -}) - -test('should rerun command 3 times before attempting to wait for page load', async () => { - expect.assertions(3) - - driver.commands.elementClick = vi.fn().mockImplementation( - () => new Promise((resolve, reject) => setTimeout( - () => { - reject(new Error('foobar most likely because of a navigation')) - }, - 100) - ) - ) - - const command = driver.register(commandMock) - - const emit = vi.fn() - - const result = await command.call({ emit } as any, '123', 'some text', ['some value']).catch((err) => err.message) - - expect(driver.commands.elementClick).toBeCalledTimes(3) - expect(result).toBe('page load timeout') - expect(emit.mock.calls).toMatchSnapshot() -}) - -test('throws error if navigation takes too long', async () => { - driver.timeouts.set('pageLoad', 150) - - driver.commands.elementClick = vi.fn().mockImplementation( - () => new Promise( - (resolve, reject) => setTimeout( - () => { - reject(new Error('foobar most likely because of a navigation')) - }, - 100 - ) - ) - ) - - const emit = vi.fn() - const command = driver.register(commandMock) - const result = await command.call({ emit } as any, '123', 'some text', ['some value']) - .catch((err) => err.message) - - expect(page.once).toBeCalledWith('load', expect.any(Function)) - expect(result).toBe('page load timeout') -}) - -test('should wait for page load to be complete before executing the command', async () => { - executionContext.evaluate.mockReset() - executionContext.evaluate = vi.fn() - .mockResolvedValueOnce('1') - .mockResolvedValueOnce('loading') - .mockResolvedValueOnce('1') - .mockResolvedValueOnce('loading') - .mockResolvedValueOnce('1') - .mockResolvedValueOnce('interactive') - .mockResolvedValueOnce('1') - .mockResolvedValueOnce('complete') - - driver.commands.elementClick = (...args: any[]) => new Promise( - (resolve) => setTimeout(() => resolve.apply(null, args), 100)) - - const emit = vi.fn() - const command = driver.register(commandMock) - await command.call({ emit } as any, '123', 'some text', ['some value']) - expect(executionContext.evaluate.mock.calls).toHaveLength(8) -}) - -test('should use page from target if we are currently in a frame', async () => { - driver.getPageHandle = vi.fn().mockReturnValue(frame) - executionContext.evaluate = vi.fn().mockReturnValueOnce('complete') - - driver.commands.elementClick = () => Promise.resolve(null) - - const emit = vi.fn() - const command = driver.register(commandMock) - await command.call({ emit } as any, '123', 'some text', ['some value']) - expect(browser.pages).toBeCalledTimes(1) -}) - -test('should rerun command if no execution context could be found', async () => { - executionContext.evaluate.mockReset() - executionContext.evaluate = vi.fn() - .mockRejectedValueOnce(new Error('ups')) - .mockRejectedValueOnce(new Error('ups')) - .mockResolvedValueOnce('1') - .mockResolvedValueOnce('complete') - - driver.commands.elementClick = (...args: any[]) => new Promise( - (resolve) => setTimeout(() => resolve.apply(null, args), 100)) - - const emit = vi.fn() - const command = driver.register(commandMock) - await command.call({ emit } as any, '123', 'some text', ['some value']) - expect(executionContext.evaluate.mock.calls).toHaveLength(4) -}) - -test('should throw if execution context can not be established', async () => { - expect.assertions(1) - - executionContext.evaluate.mockReset() - executionContext.evaluate = vi.fn() - .mockImplementation(() => new Promise( - (resolve, reject) => setTimeout( - () => reject(new Error('ups')), 100))) - - driver.timeouts.set('pageLoad', 200) - driver.commands.elementClick = (...args: any[]) => new Promise( - (resolve) => setTimeout(() => resolve.apply(null, args), 100)) - - const command = driver.register(commandMock) - - try { - const emit = vi.fn() - await command.call({ emit } as any, '123', 'some text', ['some value']) - } catch (err: any) { - expect(err.message).toBe('ups') - } -}) - -test('dialogHandler', () => { - expect(driver.activeDialog).toBe(undefined) - driver.dialogHandler('foobar' as unknown as Dialog) - expect(driver.activeDialog).toBe('foobar') -}) - -test('framenavigatedHandler for main frame', () => { - driver.elementStore.clear = vi.fn() - - const frameMock = { url: vi.fn().mockReturnValue('foobar'), parentFrame:vi.fn().mockReturnValue(null) } - driver.framenavigatedHandler(frameMock as unknown as Frame) - expect(driver.currentFrameUrl).toBe('foobar') - expect(driver.elementStore.clear).toBeCalledTimes(1) -}) - -test('framenavigatedHandler for child frame', () => { - driver.elementStore.clear = vi.fn() - - const frameMock = { url: vi.fn().mockReturnValue('foobar'), parentFrame:vi.fn().mockReturnValue({}) } - driver.framenavigatedHandler(frameMock as unknown as Frame) - expect(driver.currentFrameUrl).toBe('foobar') - expect(driver.elementStore.clear).toBeCalledTimes(1) - expect(driver.elementStore.clear).toBeCalledWith(frameMock) -}) - -test('setTimeouts with not value', () => { - driver.timeouts = { set: vi.fn(), get: vi.fn().mockReturnValue(123) } as any - driver.windows = { get: vi.fn().mockReturnValue(page) } as any - driver.setTimeouts() - expect(page.setDefaultTimeout).toBeCalledTimes(2) - expect(driver.timeouts.set).toBeCalledTimes(0) -}) - -test('setTimeouts with implicit timeout', () => { - driver.setTimeouts() - driver.setTimeouts(222) - expect(driver.timeouts).toMatchSnapshot() -}) - -test('setTimeouts with implicit and pageLoad timeout', () => { - driver.setTimeouts() - driver.setTimeouts(222) - driver.setTimeouts(222, 333) - expect(driver.timeouts).toMatchSnapshot() -}) - -test('setTimeouts with all timeouts', () => { - driver.setTimeouts() - driver.setTimeouts(222) - driver.setTimeouts(222, 333) - driver.setTimeouts(222, 333, 444) - expect(driver.timeouts).toMatchSnapshot() -}) - -test('getPageHandle', () => { - driver.windows = { get: vi.fn().mockReturnValue('foobar') } as any - expect(driver.getPageHandle()).toBe('foobar') - - driver.currentFrame = 'barfoo' as any - expect(driver.getPageHandle()).toBe('foobar') - - expect(driver.getPageHandle(true)).toBe('barfoo') - - driver.windows = { get: vi.fn().mockReturnValue(undefined) } as any - expect(() => driver.getPageHandle()).toThrow(/find page handle/) - - delete driver.currentWindowHandle - expect(() => driver.getPageHandle()).toThrow(/no current window/) -}) - -test('_targetCreatedHandler', async () => { - expect(driver.windows.size).toBe(1) - await driver['_targetCreatedHandler']({ page: vi.fn().mockReturnValue(Promise.resolve(null)) } as any) - expect(driver.windows.size).toBe(1) - await driver['_targetCreatedHandler']({ page: vi.fn().mockReturnValue(Promise.resolve('foobar')) } as any) - expect(driver.windows.size).toBe(2) -}) - -test('_targetDestroyedHandler', async () => { - const target = { page: vi.fn().mockReturnValue(Promise.resolve('foobar')) } as any - await driver['_targetCreatedHandler'](target) - expect(driver.windows.size).toBe(2) - await driver['_targetDestroyedHandler'](target) - expect(driver.windows.size).toBe(1) -}) diff --git a/packages/devtools/tests/elementStore.test.ts b/packages/devtools/tests/elementStore.test.ts deleted file mode 100644 index 93250fb78..000000000 --- a/packages/devtools/tests/elementStore.test.ts +++ /dev/null @@ -1,57 +0,0 @@ -import path from 'node:path' -import { expect, test, vi } from 'vitest' -import ElementStore from '../src/elementstore.js' -import type { ElementHandle } from 'puppeteer-core/lib/esm/puppeteer/common/ElementHandle.js' -import type { Frame } from 'puppeteer-core/lib/esm/puppeteer/api/Frame.js' - -vi.mock('@wdio/logger', () => import(path.join(process.cwd(), '__mocks__', '@wdio/logger'))) - -const elementHandleFactory = ( - { isConnected = true, frame = Symbol() }: { isConnected?: boolean, frame?: symbol } = {} -) => ({ - id: Math.random(), - async evaluate(cb: any) { - return cb({ isConnected }) - }, - executionContext() { - return { _world: { frame: () => frame } } - } -}) - -test('should keep a map of elements', async () => { - const store = new ElementStore() - const elementHandle1 = elementHandleFactory() as any as ElementHandle - store.set(elementHandle1) - expect(await store.get('ELEMENT-1')).toBe(elementHandle1) - - store.clear() - expect(store['_elementMap'].size).toBe(0) - - const elementHandle2 = elementHandleFactory() as any as ElementHandle - store.set(elementHandle2) - expect(await store.get('ELEMENT-2')).toBe(elementHandle2) - expect(await store.get('ELEMENT-3')).toBe(undefined) -}) - -test('should not return element if it is not attached to the DOM', async () => { - const store = new ElementStore() - const elementHandle = elementHandleFactory({ isConnected: false }) as any as ElementHandle - store.set(elementHandle) - expect(await store.get('ELEMENT-1')).toBe(undefined) -}) - -test('should clear elements of a specific frame', async () => { - const store = new ElementStore() - const frame1 = Symbol('frame1') - const frame2 = Symbol('frame2') - const elementHandle1 = elementHandleFactory({ frame: frame1 }) as any as ElementHandle - store.set(elementHandle1) - const elementHandle2 = elementHandleFactory({ frame: frame2 }) as any as ElementHandle - store.set(elementHandle2) - expect(store['_frameMap'].size).toBe(2) - - store.clear(frame1 as any as Frame) - - expect(await store.get('ELEMENT-1')).toBe(undefined) - expect(await store.get('ELEMENT-2')).toBe(elementHandle2) -}) diff --git a/packages/devtools/tests/finder/finder.test.ts b/packages/devtools/tests/finder/finder.test.ts deleted file mode 100644 index 1d4748d80..000000000 --- a/packages/devtools/tests/finder/finder.test.ts +++ /dev/null @@ -1,41 +0,0 @@ -import path from 'node:path' -import { expect, describe, beforeAll, test, vi } from 'vitest' -import { canAccess } from '@wdio/utils/node' - -import getFinder from '../../src/finder/index.js' -import edgeFinder from '../../src/finder/edge.js' -import { darwinGetAppPaths, darwinGetInstallations } from '../../src/finder/finder.js' - -const systemProfiler = await vi.importActual('./systemProfiler.json') - -vi.mock('@wdio/utils/node', () => import(path.join(process.cwd(), '__mocks__', '@wdio', 'utils', 'node'))) - -vi.mock('child_process', vi.fn().mockImplementation(() => ({ - execSync() { - return JSON.stringify(systemProfiler) - } -}))) - -test('should get proper finder', () => { - expect(() => getFinder('edge', 'freebsd')).toThrow(/not supported/) - expect(getFinder('edge', 'linux')).toEqual(edgeFinder.linux) -}) - -describe('darwinGetAppPaths', () => { - test('Firefox Nightly', () => { - expect(darwinGetAppPaths('Firefox Nightly')).toEqual(['/Applications/Someone Renamed Firefox.app']) - }) - - test('Microsoft Edge', () => { - expect(darwinGetAppPaths('Microsoft Edge')).toEqual(['/Applications/Microsoft Edge.app']) - }) -}) - -describe('darwinGetInstallations', () => { - beforeAll(() => { - vi.mocked(canAccess).mockImplementation((s = '') => s.startsWith(path.join('/', 'ok'))) - }) - test('darwinGetInstallations', () => { - expect(darwinGetInstallations([path.join('/', 'ok'), path.join('/', 'not-ok')], ['foobar'])).toEqual([path.join('/ok', 'foobar')]) - }) -}) diff --git a/packages/devtools/tests/finder/systemProfiler.json b/packages/devtools/tests/finder/systemProfiler.json deleted file mode 100644 index 6ccdc98bf..000000000 --- a/packages/devtools/tests/finder/systemProfiler.json +++ /dev/null @@ -1,60 +0,0 @@ -{ - "SPApplicationsDataType": [ - { - "_name": "Firefox", - "arch_kind": "arch_i64", - "info": "Firefox 81.0.1", - "lastModified": "2020-10-02T07:43:53Z", - "obtained_from": "identified_developer", - "path": "/Applications/Firefox.app", - "signed_by": [ - "Developer ID Application: Mozilla Corporation (43AQ936H96)", - "Developer ID Certification Authority", - "Apple Root CA" - ], - "version": "81.0.1" - }, - { - "_name": "Someone Renamed Firefox", - "arch_kind": "arch_i64", - "info": "Firefox Nightly 83.0a1", - "lastModified": "2020-10-01T11:33:37Z", - "obtained_from": "identified_developer", - "path": "/Applications/Someone Renamed Firefox.app", - "signed_by": [ - "Developer ID Application: Mozilla Corporation (43AQ936H96)", - "Developer ID Certification Authority", - "Apple Root CA" - ], - "version": "83.0a1" - }, - { - "_name": "Microsoft Edge", - "arch_kind": "arch_i64", - "info": "Microsoft Edge 85.0.564.68, © 2020 Microsoft Corporation. All rights reserved.", - "lastModified": "2020-10-02T12:04:44Z", - "obtained_from": "identified_developer", - "path": "/Applications/Microsoft Edge.app", - "signed_by": [ - "Developer ID Application: Microsoft Corporation (UBF8T346G9)", - "Developer ID Certification Authority", - "Apple Root CA" - ], - "version": "85.0.564.68" - }, - { - "_name": "Google Chrome", - "arch_kind": "arch_i64", - "info": "Google Chrome 85.0.4183.121, Copyright 2020 Google LLC. All rights reserved.", - "lastModified": "2020-09-19T04:09:51Z", - "obtained_from": "identified_developer", - "path": "/Applications/Google Chrome.app", - "signed_by": [ - "Developer ID Application: Google, Inc. (EQHXZ8M8AV)", - "Developer ID Certification Authority", - "Apple Root CA" - ], - "version": "85.0.4183.121" - } - ] -} diff --git a/packages/devtools/tests/index.test.ts b/packages/devtools/tests/index.test.ts deleted file mode 100644 index 01911c1e2..000000000 --- a/packages/devtools/tests/index.test.ts +++ /dev/null @@ -1,94 +0,0 @@ -import path from 'node:path' -import { expect, vi, describe, it, afterEach } from 'vitest' -// @ts-ignore mock feature -import { logMock } from '@wdio/logger' -import DevTools from '../src/index.js' - -vi.mock('@wdio/logger', () => import(path.join(process.cwd(), '__mocks__', '@wdio/logger'))) -vi.mock('puppeteer-core', () => import(path.join(process.cwd(), '__mocks__', 'puppeteer-core'))) -vi.mock('chrome-launcher', () => import(path.join(process.cwd(), '__mocks__', 'chrome-launcher'))) - -const OUTPUT_DIR = path.join('some', 'output', 'dir') -const OUTPUT_FILE = path.join(OUTPUT_DIR, 'wdio.log') - -const setUpLogCheck = (conditionFunction: () => boolean) => { - const logCheck = (...args: string[]) => { - if (!conditionFunction()) { - throw new Error( - 'Log function was called before setting ' + - 'process.env.WDIO_LOG_PATH.\n' + - 'Passed arguments to log function:\n' + - args.map((arg, index) => ` [${index}]: ${arg}`).join('\n') - ) - } - } - - logMock.error.mockImplementation(logCheck) - logMock.warn.mockImplementation(logCheck) - logMock.info.mockImplementation(logCheck) - logMock.debug.mockImplementation(logCheck) -} - -describe('DevTools', () => { - describe('newSession', () => { - afterEach(() => { - delete process.env.WDIO_LOG_PATH - - logMock.error.mockRestore() - logMock.warn.mockRestore() - logMock.info.mockRestore() - logMock.debug.mockRestore() - }) - - it('should be possible to skip setting outputDir', async () => { - setUpLogCheck(() => !('WDIO_LOG_PATH' in process.env)) - - await DevTools.newSession({ - capabilities: { browserName: 'chrome' }, - }) - - expect('WDIO_LOG_PATH' in process.env).toBe(false) - }) - - it('should be possible to set outputDir', async () => { - setUpLogCheck(() => process.env.WDIO_LOG_PATH === OUTPUT_FILE) - - await DevTools.newSession({ - capabilities: { browserName: 'chrome' }, - outputDir: OUTPUT_DIR, - }) - - expect(process.env.WDIO_LOG_PATH).toBe(OUTPUT_FILE) - }) - - it('should be possible to override outputDir with env var', async () => { - const customPath = '/some/custom/path' - - setUpLogCheck(() => process.env.WDIO_LOG_PATH === customPath) - - process.env.WDIO_LOG_PATH = customPath - - await DevTools.newSession({ - capabilities: { browserName: 'chrome' }, - outputDir: OUTPUT_DIR, - }) - - expect(process.env.WDIO_LOG_PATH).not.toBe(OUTPUT_DIR) - expect(process.env.WDIO_LOG_PATH).toBe(customPath) - }) - }) - - describe('reloadSession', () => { - it('should update puppeteer property', async () => { - const client = await DevTools.newSession({ - capabilities: { browserName: 'chrome' }, - }) - - // @ts-expect-error internal property - expect(client.puppeteer).toBe(undefined) - await DevTools.reloadSession(client) - // @ts-expect-error internal property - expect(client.puppeteer).not.toBe(undefined) - }) - }) -}) diff --git a/packages/devtools/tests/launcher.test.ts b/packages/devtools/tests/launcher.test.ts deleted file mode 100644 index 2bc20304f..000000000 --- a/packages/devtools/tests/launcher.test.ts +++ /dev/null @@ -1,756 +0,0 @@ -import path from 'node:path' -import { expect, vi, beforeEach, test } from 'vitest' -import puppeteer, { Puppeteer } from 'puppeteer-core' -import { launch as launchChromeBrowser } from 'chrome-launcher' - -import launch from '../src/launcher.js' - -vi.mock('@wdio/logger', () => import(path.join(process.cwd(), '__mocks__', '@wdio/logger'))) -vi.mock('puppeteer-core', () => import(path.join(process.cwd(), '__mocks__', 'puppeteer-core'))) -vi.mock('chrome-launcher', () => import(path.join(process.cwd(), '__mocks__', 'chrome-launcher'))) - -vi.mock('../src/finder/firefox', () => ({ - default: { - darwin: vi.fn().mockReturnValue(['/path/to/firefox']), - linux: vi.fn().mockReturnValue(['/path/to/firefox']), - win32: vi.fn().mockReturnValue(['/path/to/firefox']) - } -})) - -vi.mock('../src/finder/edge', () => ({ - default: { - darwin: vi.fn().mockReturnValue(['/path/to/edge']), - linux: vi.fn().mockReturnValue(['/path/to/edge']), - win32: vi.fn().mockReturnValue(['/path/to/edge']) - } -})) - -beforeEach(() => { - vi.mocked(puppeteer.connect).mockClear() - vi.mocked(puppeteer.launch).mockClear() - vi.mocked(launchChromeBrowser).mockClear() -}) - -test('launch chrome with default values', async () => { - const browser = await launch({ - browserName: 'chrome' - }) - expect(vi.mocked(launchChromeBrowser).mock.calls).toMatchSnapshot() - expect(vi.mocked(puppeteer.connect).mock.calls).toMatchSnapshot() - expect(Puppeteer.registerCustomQueryHandler).toBeCalledWith( - 'shadow', - { - queryAll: expect.any(Function), - queryOne: expect.any(Function) - } - ) - - const pages = await browser.pages() - expect(pages[0].close).toBeCalledTimes(1) - expect(pages[1].close).toBeCalledTimes(0) -}) - -test('launch chrome with chrome arguments', async () => { - await launch({ - browserName: 'chrome', - 'wdio:devtoolsOptions': { - headless: true, - env: { foo: 'bar' } - }, - 'goog:chromeOptions': { - binary: '/foo/bar', - args: ['--window-size=222,333'], - mobileEmulation: { - deviceName: 'Nexus 6P' - } - } - }) - expect(vi.mocked(launchChromeBrowser).mock.calls).toMatchSnapshot() - expect(puppeteer.launch).toBeCalledTimes(0) -}) - -test('launch chrome with defaultViewport in wdio:devtoolsOptions', async () => { - await launch({ - browserName: 'chrome', - 'wdio:devtoolsOptions': { - defaultViewport: { - width: 222, - height: 333, - deviceScaleFactor: 1.42, - isMobile: true - } - } - }) - expect(vi.mocked(launchChromeBrowser).mock.calls).toMatchSnapshot() - expect(puppeteer.launch).toBeCalledTimes(0) -}) - -test('launch chrome without default flags and without puppeteer default args', async () => { - const browser = await launch({ - browserName: 'chrome', - 'wdio:devtoolsOptions': { - ignoreDefaultArgs: true - } - }) - expect(vi.mocked(launchChromeBrowser).mock.calls).toMatchSnapshot() - expect(vi.mocked(puppeteer.connect).mock.calls).toMatchSnapshot() - - const pages = await browser.pages() - expect(pages[0].close).toBeCalled() - expect(pages[1].close).toBeCalledTimes(0) -}) - -test('overriding chrome default flags', async () => { - const browser = await launch({ - browserName: 'chrome', - 'wdio:devtoolsOptions': { - ignoreDefaultArgs: ['--disable-sync', '--enable-features=NetworkService,NetworkServiceInProcess'] - }, - 'goog:chromeOptions': { - args: ['--enable-features=NetworkService'] - } - }) - expect(vi.mocked(launchChromeBrowser).mock.calls).toMatchSnapshot() - - const pages = await browser.pages() - expect(pages[0].close).toBeCalled() - expect(pages[1].close).toBeCalledTimes(0) -}) - -test('overriding chrome default flags (backwards compat)', async () => { - const browser = await launch({ - browserName: 'chrome', - // ignore type check as we want to test whether it is still - // possible with old format - // @ts-ignore - ignoreDefaultArgs: ['--disable-sync', '--enable-features=NetworkService,NetworkServiceInProcess'], - 'goog:chromeOptions': { - // ignore type check as we want to test whether it is still - // possible with old format - // @ts-ignore - headless: true, - args: ['--enable-features=NetworkService'], - prefs: { - bar: 'foo' - } - } - }) - expect(vi.mocked(launchChromeBrowser).mock.calls).toMatchSnapshot() - - const pages = await browser.pages() - expect(pages[0].close).toBeCalled() - expect(pages[1].close).toBeCalledTimes(0) -}) - -test('launch chrome with custom user data dir', async () => { - await launch({ - browserName: 'chrome', - 'goog:chromeOptions': { - args: ['enable-features=NetworkService', 'someOtherFlag', 'user-data-dir=/foo/bar', 'anotherFlag'] - } - }) - expect(vi.mocked(launchChromeBrowser).mock.calls).toMatchSnapshot() -}) - -test('launch chrome with chrome port', async () => { - const browser = await launch({ - browserName: 'chrome', - 'goog:chromeOptions': {}, - 'wdio:devtoolsOptions': { - customPort: 8041 - } - }) - expect(vi.mocked(launchChromeBrowser).mock.calls).toMatchSnapshot() - expect(puppeteer.launch).toBeCalledTimes(0) - - const pages = await browser.pages() - expect(pages[1].close).toBeCalledTimes(0) -}) - -test('throws an error if an unknown deviceName is picked', async () => { - const err = await launch({ - browserName: 'chrome', - 'goog:chromeOptions': { - mobileEmulation: { - deviceName: 'Cool Nexus 5' - }, - prefs: { - foo: 'bar' - } - } - }).catch((err) => err) - expect(err.message).toContain('Unknown device name "Cool Nexus 5"') -}) - -test('launch Firefox with custom arguments', async () => { - await launch({ - browserName: 'firefox', - 'wdio:devtoolsOptions': { - headless: true, - defaultViewport: { - width: 123, - height: 456 - } - } - }) - expect(vi.mocked(puppeteer.launch).mock.calls).toMatchSnapshot() -}) - -test('launch Edge with default values', async () => { - await launch({ - browserName: 'edge' - }) - expect(vi.mocked(puppeteer.launch).mock.calls).toMatchSnapshot() -}) - -test('launch Edge with custom arguments', async () => { - await launch({ - browserName: 'edge', - 'ms:edgeOptions': { - args: ['foobar'] - }, - 'wdio:devtoolsOptions': { - headless: true, - defaultViewport: { - width: 123, - height: 456 - } - } - }) - expect(vi.mocked(puppeteer.launch).mock.calls).toMatchSnapshot() -}) - -test('throws if browser is unknown', async () => { - expect.assertions(1) - - try { - await launch({ browserName: 'foobar' }) - } catch (err: any) { - expect(err.message).toContain('Couldn\'t identify browserName') - } -}) - -test('launch Firefox Nightly without Puppeteer default args', async () => { - await launch({ - browserName: 'firefox', - 'moz:firefoxOptions': { - binary: '/foo/firefox-nightly' - }, - 'wdio:devtoolsOptions': { - headless: true, - ignoreDefaultArgs: true - } - }) - expect(vi.mocked(puppeteer.launch).mock.calls).toMatchSnapshot() -}) - -test('launch Firefox Nightly binary without Puppeteer default args', async () => { - await launch({ - browserName: 'firefox', - 'moz:firefoxOptions': { - binary: 'firefox', - prefs: { - foo: 'bar' - } - }, - 'wdio:devtoolsOptions': { - ignoreDefaultArgs: true, - headless: true - } - }) - expect(vi.mocked(puppeteer.launch).mock.calls).toMatchSnapshot() -}) - -test('launch Edge without Puppeteer default args', async () => { - await launch({ - browserName: 'edge', - 'wdio:devtoolsOptions': { - ignoreDefaultArgs: true, - headless: true - } - }) - expect(vi.mocked(puppeteer.launch).mock.calls).toMatchSnapshot() -}) - -test('launch Edge without Puppeteer default args (backwards compat)', async () => { - await launch({ - browserName: 'edge', - // ignore type check as we want to test whether it is still - // possible with old format - // @ts-ignore - ignoreDefaultArgs: true, - 'ms:edgeOptions': { - // ignore type check as we want to test whether it is still - // possible with old format - // @ts-ignore - headless: true, - prefs: { - foo: 'bar' - } - } - }) - expect(vi.mocked(puppeteer.launch).mock.calls).toMatchSnapshot() -}) - -test('connect to existing browser session', async () => { - await launch({ - browserName: 'edge', - 'ms:edgeOptions': { - debuggerAddress: 'localhost:12345' - } - }) - expect(puppeteer.launch).not.toBeCalled() - expect(vi.mocked(puppeteer.connect)) - .toBeCalledWith({ browserURL: 'http://localhost:12345' }) -}) - -test('connect to an existing devtools websocket', async () => { - await launch({ - browserName: 'edge', - 'ms:edgeOptions': {}, - 'wdio:devtoolsOptions': { - browserWSEndpoint: 'wss://cloud.vendor.com' - } - }) - expect(puppeteer.launch).not.toBeCalled() - expect(vi.mocked(puppeteer.connect)) - .toBeCalledWith({ browserWSEndpoint: 'wss://cloud.vendor.com' }) -}) - -test('connect to an existing devtools browser url', async () => { - const devtoolsOptions = { - browserURL: 'http://localhost:1234', - ignoreHTTPSErrors: true - } - await launch({ - browserName: 'edge', - 'ms:edgeOptions': {}, - 'wdio:devtoolsOptions': devtoolsOptions - }) - expect(puppeteer.launch).not.toBeCalled() - expect(vi.mocked(puppeteer.connect)) - .toBeCalledWith(devtoolsOptions) -}) - -test('launch Chrome with default Args to ignore certificate errors (devtools option ignoreHTTPSErrors)', async () => { - await launch({ - browserName: 'chrome', - 'wdio:devtoolsOptions': { - ignoreDefaultArgs: false, - headless: true, - ignoreHTTPSErrors: true - } - }) - - expect(launchChromeBrowser).toBeCalledWith( - expect.objectContaining({ - chromeFlags: expect.arrayContaining([ - '--ignore-certificate-errors', - ]), - }) - ) -}) - -test('launch Chrome while ignoring default args to ignore certificate errors (devtools option ignoreHTTPSErrors)', async () => { - await launch({ - browserName: 'chrome', - 'wdio:devtoolsOptions': { - ignoreDefaultArgs: true, - headless: true, - ignoreHTTPSErrors: true - } - }) - - expect(launchChromeBrowser).toBeCalledWith( - expect.objectContaining({ - chromeFlags: expect.arrayContaining([ - '--ignore-certificate-errors', - ]), - }) - ) -}) - -test('launch Edge with default Args to ignore certificate errors (devtools option ignoreHTTPSErrors)', async () => { - await launch({ - browserName: 'edge', - 'wdio:devtoolsOptions': { - ignoreDefaultArgs: false, - headless: true, - ignoreHTTPSErrors: true - } - }) - expect(puppeteer.launch).toBeCalledWith( - expect.objectContaining({ - args: ['--ignore-certificate-errors'], - }) - ) -}) - -test('launch Edge while ignoring default args to ignore certificate errors (devtools option ignoreHTTPSErrors)', async () => { - await launch({ - browserName: 'edge', - 'wdio:devtoolsOptions': { - ignoreDefaultArgs: true, - headless: true, - ignoreHTTPSErrors: true - } - }) - expect(puppeteer.launch).toBeCalledWith( - expect.objectContaining({ - args: ['--ignore-certificate-errors'], - }) - ) -}) - -test('launch Firefox with default Args to ignore certificate errors (devtools option ignoreHTTPSErrors)', async () => { - await launch({ - browserName: 'firefox', - 'wdio:devtoolsOptions': { - ignoreDefaultArgs: false, - headless: true, - ignoreHTTPSErrors: true - } - }) - expect(puppeteer.launch).toBeCalledWith( - expect.objectContaining({ - 'ignoreHTTPSErrors': true - }) - ) -}) - -test('launch Firefox while ignoring default args to ignore certificate errors (devtools option ignoreHTTPSErrors)', async () => { - await launch({ - browserName: 'firefox', - 'wdio:devtoolsOptions': { - ignoreDefaultArgs: true, - headless: true, - ignoreHTTPSErrors: true - } - }) - expect(puppeteer.launch).toBeCalledWith( - expect.objectContaining({ - 'ignoreHTTPSErrors': true - }) - ) -}) - -test('launch Chrome with default Args to ignore certificate errors (W3C capability acceptInsecureCerts)', async () => { - await launch({ - browserName: 'chrome', - acceptInsecureCerts: true, - 'wdio:devtoolsOptions': { - ignoreDefaultArgs: false, - headless: true, - } - }) - - expect(launchChromeBrowser).toBeCalledWith( - expect.objectContaining({ - chromeFlags: expect.arrayContaining([ - '--ignore-certificate-errors', - ]), - }) - ) -}) - -test('launch Chrome while ignoring default args to ignore certificate errors (W3C capability acceptInsecureCerts)', async () => { - await launch({ - browserName: 'chrome', - acceptInsecureCerts: true, - 'wdio:devtoolsOptions': { - ignoreDefaultArgs: true, - headless: true, - } - }) - - expect(launchChromeBrowser).toBeCalledWith( - expect.objectContaining({ - chromeFlags: expect.arrayContaining([ - '--ignore-certificate-errors', - ]), - }) - ) -}) - -test('launch Edge with default Args to ignore certificate errors (W3C capability acceptInsecureCerts)', async () => { - await launch({ - browserName: 'edge', - acceptInsecureCerts: true, - 'wdio:devtoolsOptions': { - ignoreDefaultArgs: false, - headless: true, - } - }) - expect(puppeteer.launch).toBeCalledWith( - expect.objectContaining({ - args: ['--ignore-certificate-errors'], - }) - ) -}) - -test('launch Edge while ignoring default args to ignore certificate errors (W3C capability acceptInsecureCerts)', async () => { - await launch({ - browserName: 'edge', - acceptInsecureCerts: true, - 'wdio:devtoolsOptions': { - ignoreDefaultArgs: true, - headless: true, - } - }) - expect(puppeteer.launch).toBeCalledWith( - expect.objectContaining({ - args: ['--ignore-certificate-errors'], - }) - ) -}) - -test('launch Firefox with default Args to ignore certificate errors (W3C capability acceptInsecureCerts)', async () => { - await launch({ - browserName: 'firefox', - acceptInsecureCerts: true, - 'wdio:devtoolsOptions': { - ignoreDefaultArgs: false, - headless: true, - } - }) - expect(puppeteer.launch).toBeCalledWith( - expect.objectContaining({ - 'ignoreHTTPSErrors': true - }) - ) -}) - -test('launch Firefox while ignoring default args to ignore certificate errors (W3C capability acceptInsecureCerts)', async () => { - await launch({ - browserName: 'firefox', - acceptInsecureCerts: true, - 'wdio:devtoolsOptions': { - ignoreDefaultArgs: true, - headless: true, - } - }) - - expect(puppeteer.launch).toBeCalledWith( - expect.objectContaining({ - 'ignoreHTTPSErrors': true, - }) - ) -}) - -// qwe - -test('launch Chrome with default Args and not specifying devtools option ignoreHTTPSErrors', async () => { - await launch({ - browserName: 'chrome', - 'wdio:devtoolsOptions': { - ignoreDefaultArgs: false, - headless: true, - } - }) - - expect(launchChromeBrowser).toBeCalledWith( - expect.objectContaining({ - chromeFlags: expect.not.arrayContaining([ - '--ignore-certificate-errors', - ]), - }) - ) -}) - -test('launch Chrome while ignoring default args and not specifying devtools option ignoreHTTPSErrors', async () => { - await launch({ - browserName: 'chrome', - 'wdio:devtoolsOptions': { - ignoreDefaultArgs: true, - headless: true, - } - }) - - expect(launchChromeBrowser).toBeCalledWith( - expect.objectContaining({ - chromeFlags: expect.not.arrayContaining([ - '--ignore-certificate-errors', - ]), - }) - ) -}) - -test('launch Edge with default Args and not specifying devtools option ignoreHTTPSErrors', async () => { - await launch({ - browserName: 'edge', - 'wdio:devtoolsOptions': { - ignoreDefaultArgs: false, - headless: true, - } - }) - - expect(puppeteer.launch).toBeCalledWith( - expect.objectContaining({ - args: expect.not.arrayContaining( - ['--ignore-certificate-errors'] - ), - }) - ) -}) - -test('launch Edge while ignoring default args and not specifying devtools option ignoreHTTPSErrors', async () => { - await launch({ - browserName: 'edge', - 'wdio:devtoolsOptions': { - ignoreDefaultArgs: true, - headless: true, - } - }) - expect(puppeteer.launch).toBeCalledWith( - expect.objectContaining({ - args: expect.not.arrayContaining( - ['--ignore-certificate-errors'] - ), - }) - ) -}) - -test('launch Firefox with default Args and not specifying devtools option ignoreHTTPSErrors', async () => { - await launch({ - browserName: 'firefox', - 'wdio:devtoolsOptions': { - ignoreDefaultArgs: false, - headless: true, - } - }) - expect(puppeteer.launch).toBeCalledWith( - expect.objectContaining({ - 'ignoreHTTPSErrors': false - }) - ) -}) - -test('launch Firefox while ignoring default args and not specifying devtools option ignoreHTTPSErrors', async () => { - await launch({ - browserName: 'firefox', - 'wdio:devtoolsOptions': { - ignoreDefaultArgs: true, - headless: true, - } - }) - expect(puppeteer.launch).toBeCalledWith( - expect.objectContaining({ - 'ignoreHTTPSErrors': false - }) - ) -}) - -test('launch Chrome with default Args and not specifying W3C capability acceptInsecureCerts', async () => { - await launch({ - browserName: 'chrome', - 'wdio:devtoolsOptions': { - ignoreDefaultArgs: false, - headless: true, - } - }) - - expect(launchChromeBrowser).toBeCalledWith( - expect.objectContaining({ - chromeFlags: expect.not.arrayContaining([ - '--ignore-certificate-errors', - ]), - }) - ) -}) - -test('launch Chrome while ignoring default args and not specifying W3C capability acceptInsecureCerts', async () => { - await launch({ - browserName: 'chrome', - 'wdio:devtoolsOptions': { - ignoreDefaultArgs: true, - headless: true, - } - }) - - expect(launchChromeBrowser).toBeCalledWith( - expect.objectContaining({ - chromeFlags: expect.not.arrayContaining([ - '--ignore-certificate-errors', - ]), - }) - ) -}) - -test('launch Edge with default Args and not specifying W3C capability acceptInsecureCerts', async () => { - await launch({ - browserName: 'edge', - 'wdio:devtoolsOptions': { - ignoreDefaultArgs: false, - headless: true, - } - }) - expect(puppeteer.launch).toBeCalledWith( - expect.objectContaining({ - args: expect.not.arrayContaining( - ['--ignore-certificate-errors'] - ) - }) - ) -}) - -test('launch Edge while ignoring default args and not specifying W3C capability acceptInsecureCerts', async () => { - await launch({ - browserName: 'edge', - 'wdio:devtoolsOptions': { - ignoreDefaultArgs: true, - headless: true, - } - }) - expect(puppeteer.launch).toBeCalledWith( - expect.objectContaining({ - args: expect.not.arrayContaining( - ['--ignore-certificate-errors'] - ) - }) - ) -}) - -test('launch Firefox with default args and not specifying W3C capability acceptInsecureCerts', async () => { - await launch({ - browserName: 'firefox', - 'wdio:devtoolsOptions': { - ignoreDefaultArgs: false, - headless: true, - } - }) - expect(puppeteer.launch).toBeCalledWith( - expect.objectContaining({ - 'ignoreHTTPSErrors': false - }) - ) -}) - -test('launch Firefox while ignoring default args and not specifying W3C capability acceptInsecureCerts', async () => { - await launch({ - browserName: 'firefox', - 'wdio:devtoolsOptions': { - ignoreDefaultArgs: true, - headless: true, - } - }) - - expect(puppeteer.launch).toBeCalledWith( - expect.objectContaining({ - 'ignoreHTTPSErrors': false, - }) - ) -}) - -test('launch Chrome with defaultViewPort as null', async () => { - await launch({ - browserName: 'chrome', - 'wdio:devtoolsOptions': { defaultViewport: null } - }) - expect(launchChromeBrowser).toBeCalledWith( - expect.objectContaining({ - chromeFlags: expect.not.arrayContaining( - ['--window-size=1200,900', '--window-position=0,0'] - ) - }) - ) -}) diff --git a/packages/devtools/tests/utils.test.ts b/packages/devtools/tests/utils.test.ts deleted file mode 100644 index f1b5d342a..000000000 --- a/packages/devtools/tests/utils.test.ts +++ /dev/null @@ -1,426 +0,0 @@ -import { describe, it, expect, afterEach, vi, test } from 'vitest' - -import type path from 'node:path' -import which from 'which' -import { launch } from 'chrome-launcher' -import { canAccess } from '@wdio/utils/node' - -import { - validate, getPrototype, findElement, findElements, getStaleElementError, - sanitizeError, transformExecuteArgs, transformExecuteResult, getPages, - uniq, findByWhich, sleep, launchChromeUsingWhich -} from '../src/utils.js' - -vi.mock('which') -vi.mock('chrome-launcher') -vi.mock('@wdio/logger', async () => { - const pathModule = await vi.importActual('node:path') as typeof path - return import(pathModule.join(process.cwd(), '__mocks__', '@wdio/logger')) -}) -vi.mock('@wdio/utils', async () => { - const pathModule = await vi.importActual('node:path') as typeof path - return import(pathModule.join(process.cwd(), '__mocks__', '@wdio/utils')) -}) -vi.mock('@wdio/utils/node', async () => { - const pathModule = await vi.importActual('node:path') as typeof path - return import(pathModule.join(process.cwd(), '__mocks__', '@wdio/utils', 'node')) -}) -vi.mock('pptrDebug', async () => { - const pathModule = await vi.importActual('node:path') as typeof path - return import(pathModule.join(process.cwd(), '__mocks__', 'pptrDebug')) -}) - -/** - * some WebDriver commands are either not part of a recommended standard - * or not used enough by end users that it would make sense to implement - * parity to the devtools protocol - */ -const IGNORE_MISSING_COMMANDS = [ - 'addCredential', 'addVirtualAuthenticator', 'createMockSensor', 'deleteMockSensor', - 'generateTestReport', 'getCredentials', 'getMockSensor', 'removeAllCredentials', - 'removeCredential', 'removeVirtualAuthenticator', 'setPermissions', 'setTimeZone', - 'setUserVerified', 'updateMockSensor' -] - -const command = { - endpoint: '/session/:sessionId/element/:elementId/element', - method: 'POST', - command: 'findElementFromElement', - ref: 'https://w3c.github.io/webdriver/webdriver-spec.html#dfn-find-element-from-element', - variables: [{ - 'name': 'elementId', - 'description': 'the id of an element returned in a previous call to Find Element(s)' - }], - parameters: [{ - 'name': 'using', - 'type': 'string', - 'description': 'a valid element location strategy', - 'required': true - }, { - 'name': 'value', - 'type': 'string', - 'description': 'the actual selector that will be used to find an element', - 'required': true - }, { - 'name': 'customParam', - 'type': 'number', - 'description': 'a random not required param', - 'required': false - }] -} - -const pageMock = { - waitForSelector: vi.fn(), - waitForXPath: vi.fn(), - $$eval: vi.fn(), - $$: vi.fn(), - $x: vi.fn(), - $: vi.fn() -} - -vi.mock('fs', () => { - return { - default: { - existsSync: (pkgName: string) => pkgName === 'pptrDebug' - } - } -}) - -vi.mock('debug', () => ({ - default: { log: vi.fn() } -})) - -describe('validate', () => { - it('should fail if wrong arguments are passed in', () => { - expect(() => validate(command.command, command.parameters, command.variables, command.ref, [])) - .toThrow(/Wrong parameters applied for findElementFromElement/) - }) - - it('should fail if arguments are malformed', () => { - expect(() => validate(command.command, command.parameters, command.variables, command.ref, ['123', 123, '123'])) - .toThrow(/Malformed type for "using" parameter of command/) - }) - - it('should fail if not required param has wrong type', () => { - expect(() => validate(command.command, command.parameters, command.variables, command.ref, ['123', '123', '123', 'foobar'])) - .toThrow(/Malformed type for "customParam" parameter of command/) - }) - - it('should do a proper request', () => { - const body = validate(command.command, command.parameters, command.variables, command.ref, ['123', 'css selector', '#body', undefined]) - expect(body).toMatchSnapshot() - }) - - it('should do a proper request with non required params', () => { - const body = validate(command.command, command.parameters, command.variables, command.ref, ['123', 'css selector', '#body', 123]) - expect(body).toMatchSnapshot() - }) -}) - -test('getPrototype', () => { - let i = 0 - const commands = getPrototype(() => ++i) - const filteredCommands = Object.entries(commands) - .reduce((cmds, [name, description]) => { - if (IGNORE_MISSING_COMMANDS.includes(name)) { - return cmds - } - cmds[name] = description - return cmds - }, {} as Record) - expect(filteredCommands).toMatchSnapshot() -}) - -describe('findElement utils', () => { - afterEach(() => { - pageMock.waitForSelector.mockClear() - pageMock.waitForXPath.mockClear() - pageMock.$$eval.mockClear() - pageMock.$.mockClear() - pageMock.$x.mockClear() - pageMock.$$.mockClear() - }) - - describe('findElement', () => { - it('tries to find element using css selector', async () => { - const scope = { - timeouts: { get: vi.fn() }, - elementStore: { set: vi.fn().mockReturnValue('foobar') } - } - pageMock.$.mockReturnValue(Promise.resolve(42)) - - expect(await findElement.call(scope as any, pageMock as any as any, 'css selector', 'barfoo')) - .toEqual({ 'element-6066-11e4-a52e-4f735466cecf': 'foobar' }) - - expect(pageMock.waitForSelector).toBeCalledTimes(0) - expect(pageMock.$).toBeCalledWith('barfoo') - expect(pageMock.$x).toBeCalledTimes(0) - }) - - it('tries to find element using xpath', async () => { - const scope = { - timeouts: { get: vi.fn() }, - elementStore: { set: vi.fn().mockReturnValue('foobar') } - } - pageMock.$x.mockReturnValue(Promise.resolve([42])) - - expect(await findElement.call(scope as any, pageMock as any, 'xpath', '//img')) - .toEqual({ 'element-6066-11e4-a52e-4f735466cecf': 'foobar' }) - - expect(pageMock.waitForSelector).toBeCalledTimes(0) - expect(pageMock.$).toBeCalledTimes(0) - expect(pageMock.$x).toBeCalledWith('//img') - }) - - it('should fail if not found', async () => { - const scope = { - timeouts: { get: vi.fn() }, - elementStore: { set: vi.fn() } - } - pageMock.$.mockReturnValue(Promise.resolve(null)) - - const errorMessage = await findElement.call(scope as any, pageMock as any, 'css selector', 'barfoo') - expect((errorMessage as Error).message) - .toContain('Element with selector "barfoo" not found') - }) - - it('should not fail with the same error if Puppeteer can not find the element', async () => { - const scope = { - timeouts: { get: vi.fn() }, - elementStore: { set: vi.fn() } - } - pageMock.$.mockReturnValue(Promise.reject(new Error('failed to find element'))) - - const errorMessage = await findElement.call(scope as any, pageMock as any, 'css selector', 'barfoo') - expect((errorMessage as Error).message) - .toContain('Element with selector "barfoo" not found') - }) - - it('sets implicit waits', async () => { - const scope = { - timeouts: { get: vi.fn().mockReturnValue(1234) }, - elementStore: { set: vi.fn() } - } - pageMock.$.mockReturnValue(Promise.resolve('foobar')) - await findElement.call(scope as any, pageMock as any, 'css selector', 'barfoo') - expect(pageMock.waitForSelector).toBeCalledTimes(1) - expect(pageMock.waitForXPath).toBeCalledTimes(0) - }) - - it('sets implicit waits with xpath', async () => { - const scope = { - timeouts: { get: vi.fn().mockReturnValue(1234) }, - elementStore: { set: vi.fn() } - } - pageMock.$.mockReturnValue(Promise.resolve('foobar')) - await findElement.call(scope as any, pageMock as any, 'xpath', 'barfoo') - expect(pageMock.waitForSelector).toBeCalledTimes(0) - expect(pageMock.waitForXPath).toBeCalledTimes(1) - }) - }) - - describe('findElements', () => { - it('should find elements', async () => { - const scope = { - timeouts: { get: vi.fn() }, - elementStore: { set: vi.fn().mockReturnValue('foobar') } - } - pageMock.$$.mockReturnValue(Promise.resolve([42, 11])) - expect(await findElements.call(scope as any, pageMock as any, 'css selector', 'barfoo')).toMatchSnapshot() - expect(pageMock.waitForSelector).toBeCalledTimes(0) - expect(pageMock.$$).toBeCalledWith('barfoo') - expect(pageMock.$x).toBeCalledTimes(0) - }) - - it('should find elements with xpath', async () => { - const scope = { - timeouts: { get: vi.fn() }, - elementStore: { set: vi.fn().mockReturnValue('foobar') } - } - pageMock.$x.mockReturnValue(Promise.resolve([42, 11])) - expect(await findElements.call(scope as any, pageMock as any, 'xpath', 'barfoo')).toMatchSnapshot() - expect(pageMock.waitForSelector).toBeCalledTimes(0) - expect(pageMock.$$).toBeCalledTimes(0) - expect(pageMock.$x).toBeCalledWith('barfoo') - }) - - it('should return immediately if no elements were found', async () => { - const scope = { - timeouts: { get: vi.fn() }, - elementStore: { set: vi.fn() } - } - pageMock.$$.mockReturnValue(Promise.resolve([])) - expect(await findElements.call(scope as any, pageMock as any, 'css selector', 'barfoo')).toEqual([]) - expect(pageMock.waitForSelector).toBeCalledTimes(0) - expect(scope.elementStore.set).toBeCalledTimes(0) - }) - - it('sets implicit waits', async () => { - const scope = { - timeouts: { get: vi.fn().mockReturnValue(1234) }, - elementStore: { set: vi.fn() } - } - pageMock.$$.mockReturnValue(Promise.resolve(['foobar'])) - await findElements.call(scope as any, pageMock as any, 'css selector', 'barfoo') - expect(pageMock.waitForSelector).toBeCalledTimes(1) - }) - - it('sets implicit waits with xpath', async () => { - const scope = { - timeouts: { get: vi.fn().mockReturnValue(1234) }, - elementStore: { set: vi.fn() } - } - pageMock.$x.mockReturnValue(Promise.resolve(['foobar'])) - await findElements.call(scope as any, pageMock as any, 'xpath', 'barfoo') - expect(pageMock.waitForSelector).toBeCalledTimes(0) - expect(pageMock.waitForXPath).toBeCalledTimes(1) - }) - }) -}) - -describe('sanitizeError', () => { - it('should set stale element error', () => { - const err = new Error('Node is detached from document bla bla') - expect(sanitizeError(err).stack) - .toContain('stale element reference: The element reference is stale') - }) - - it('should not contain puppeteer traces', () => { - const err = new Error('some error') - const newStack = err?.stack?.split('\n') || [] - newStack.push('at /foo/bar/devtools/node_modules/puppeteer-core/bla.js') - err.stack = newStack.join('\n') - expect(sanitizeError(err).stack).not.toContain('devtools/node_modules/puppeteer-core') - }) -}) - -test('transformExecuteArgs', async () => { - const scope = { elementStore: new Map() } - scope.elementStore.set('foobar', 'barfoo') - - expect(await transformExecuteArgs.call(scope as any, [ - 'foo', - { 'element-6066-11e4-a52e-4f735466cecf': 'foobar' }, - true, - 42 - ])).toEqual(['foo', 'barfoo', true, 42]) -}) - -test('transformExecuteArgs throws stale element if element is not in store', async () => { - const scope = { elementStore: new Map() } - scope.elementStore.set('foobar', 'barfoo') - - expect(transformExecuteArgs.call(scope as any, [ - 'foo', - { 'element-6066-11e4-a52e-4f735466cecf': 'not-existing' }, - true, - 42 - ])).rejects.toThrow() -}) - -test('transformExecuteArgs should allow undefined params', async () => { - const scope = { elementStore: new Map() } - expect(await transformExecuteArgs.call(scope as any, undefined)).toEqual([]) -}) - -describe('transformExecuteResult', () => { - test('multiple results', async () => { - const scope = { - timeouts: { get: vi.fn() }, - elementStore: { set: vi.fn().mockReturnValue('foobar') } - } - pageMock.$.mockReturnValue(Promise.resolve(42)) - expect(await transformExecuteResult.call(scope as any, pageMock as any, [ - 'foobar', - '__executeElement', - 42 - ])).toMatchSnapshot() - }) - - test('single result', async () => { - const scope = { - timeouts: { get: vi.fn() }, - elementStore: { set: vi.fn().mockReturnValue('foobar') } - } - pageMock.$.mockReturnValue(Promise.resolve(42)) - expect(await transformExecuteResult.call(scope as any, pageMock as any, 'foobar')) - .toEqual('foobar') - }) - - afterEach(() => { - pageMock.waitForSelector.mockClear() - pageMock.$.mockClear() - pageMock.$$.mockClear() - pageMock.$$eval.mockClear() - }) -}) - -test('getPages', async () => { - const browser = { - pages: vi.fn() - .mockReturnValueOnce([]) - .mockReturnValueOnce([]) - .mockReturnValueOnce([{}]) - } - await getPages(browser as any, 10) - expect(browser.pages).toBeCalledTimes(3) -}) - -test('getStaleElementError', () => { - const err = getStaleElementError('foobar') - expect(err instanceof Error).toBe(true) - expect(err.name).toContain('stale element reference') -}) - -test('uniq', () => { - const listA = [1, 2, 3] - expect(listA).toBe(listA) - expect(listA).not.toBe(uniq(listA as any)) -}) - -test('findByWhich', () => { - vi.mocked(canAccess).mockImplementation(() => true) - expect(findByWhich(['firefox'], [{ regex: /firefox/, weight: 51 }])) - .toEqual([]) - - vi.mocked(which.sync).mockReturnValue('/path/to/other/firefox') - expect(findByWhich(['firefox'], [{ regex: /firefox/, weight: 51 }])) - .toEqual(['/path/to/other/firefox']) - - vi.mocked(canAccess).mockImplementation(() => { - throw new Error('uups') - }) - expect(findByWhich(['firefox'], [{ regex: /firefox/, weight: 51 }])) - .toEqual([]) -}) - -test('sleep', async () => { - const start = Date.now() - await sleep(100) - expect(Date.now() - start).toBeGreaterThanOrEqual(90) -}) - -describe('launchChromeUsingWhich', () => { - it('should throw if error is not related to chrome binary not found', async () => { - await expect(() => launchChromeUsingWhich(new Error('ups'), {})) - .rejects.toThrow('ups') - }) - - it('should throw if user has specified path explicitly', async () => { - await expect(() => launchChromeUsingWhich( - new Error('No Chrome installations found.'), - { chromePath: '/foo/bar' } - )).rejects.toThrow('No Chrome installations found.') - }) - - it('should use which properly to find path', async () => { - vi.mocked(which).mockImplementation(async (binary: string) => { - if (binary === 'google-chrome') { - return '/foo/bar/google-chrome' - } - throw new Error('not found') - }) - await launchChromeUsingWhich(new Error('No Chrome installations found.'), {}) - expect(launch).toBeCalledWith({ chromePath: '/foo/bar/google-chrome' }) - }) -}) diff --git a/packages/devtools/tsconfig.json b/packages/devtools/tsconfig.json deleted file mode 100644 index 0afd57e3e..000000000 --- a/packages/devtools/tsconfig.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "extends": "../../tsconfig", - "compilerOptions": { - "baseUrl": ".", - "outDir": "./build", - "rootDir": "./src", - "types": ["@wdio/globals/types"] - }, - "include": [ - "src/**/*", - "../../@types", - "src/cjs/package.json" - ] -} diff --git a/packages/devtools/tsconfig.prod.json b/packages/devtools/tsconfig.prod.json deleted file mode 100644 index bb03dcf8c..000000000 --- a/packages/devtools/tsconfig.prod.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "extends": "../../tsconfig.prod", - "compilerOptions": { - "baseUrl": ".", - "outDir": "./build", - "rootDir": "./src", - "types": ["@wdio/globals/types"] - }, - "include": [ - "src/**/*", - "../../@types", - "src/cjs/package.json" - ] -} diff --git a/packages/wdio-allure-reporter/tests/suite.test.ts b/packages/wdio-allure-reporter/tests/suite.test.ts index 2e42d38c8..e7ccafdd1 100644 --- a/packages/wdio-allure-reporter/tests/suite.test.ts +++ b/packages/wdio-allure-reporter/tests/suite.test.ts @@ -14,7 +14,7 @@ import { TYPE } from '../src/types.js' * methods without having to ignore them for test coverage */ // eslint-disable-next-line -import { clean, getResults, mapBy } from './helpers/wdio-allure-helper' +import { clean, getResults, mapBy } from './helpers/wdio-allure-helper.js' import { runnerEnd, runnerStart } from './__fixtures__/runner.js' import { suiteEnd, suiteStart } from './__fixtures__/suite.js' @@ -455,8 +455,8 @@ describe('Pending tests', () => { expect(results).toHaveLength(2) - const passedResult = results.find((result) => result.status === Status.PASSED) - const skippedResult = results.find((result) => result.status === Status.SKIPPED) + const passedResult = results.find((result: any) => result.status === Status.PASSED) + const skippedResult = results.find((result: any) => result.status === Status.SKIPPED) expect(passedResult.name).toEqual(passed.title) expect(passedResult.stage).toEqual(Stage.FINISHED) @@ -515,11 +515,11 @@ describe('Hook reporting', () => { const { results } = getResults(outputDir) expect(results).toHaveLength(2) - const testCaseStep = results.find((tc => tc.name === 'My Login application')) + const testCaseStep = results.find(((tc: any) => tc.name === 'My Login application')) expect(testCaseStep).toBeDefined() expect(testCaseStep.status).toEqual(Status.BROKEN) - const hookCase = results.find((tc => tc.name === '"before each" hook')) + const hookCase = results.find(((tc: any) => tc.name === '"before each" hook')) expect(hookCase).toBeDefined() expect(hookCase.status).toEqual(Status.BROKEN) }) @@ -596,412 +596,410 @@ const assertionResults: any = { } } -for (const protocol of ['webdriver', 'devtools']) { - describe(`${protocol} command reporting`, () => { - let outputDir: any +describe('command reporting', () => { + let outputDir: any - beforeEach(() => { - outputDir = temporaryDirectory() - }) - - afterEach(() => { - clean(outputDir) - }) - - it('should not add step if no tests started', () => { - const allureOptions = { - stdout: true, - outputDir - } - const reporter = new AllureReporter(allureOptions) - - reporter.onRunnerStart(runnerStart()) - reporter.onSuiteStart(suiteStart()) - reporter.onBeforeCommand(commandStart(protocol === 'devtools')) - reporter.onAfterCommand(commandEnd(protocol === 'devtools')) - reporter.onTestSkip(testPending()) - reporter.onSuiteEnd(suiteEnd()) - reporter.onRunnerEnd(runnerEnd()) - - const { results } = getResults(outputDir) - - expect(results).toHaveLength(1) - expect(results[0].steps).toHaveLength(0) - }) - - it('should not add step if isMultiremote = true', () => { - const allureOptions = { - stdout: true, - outputDir - } - const reporter = new AllureReporter(allureOptions) - reporter.onRunnerStart(Object.assign(runnerStart(), { isMultiremote: true })) - reporter.onSuiteStart(suiteStart()) - reporter.onTestStart(testStart()) - reporter.onBeforeCommand(commandStart(protocol === 'devtools')) - reporter.onAfterCommand(commandEnd(protocol === 'devtools')) - reporter.onTestSkip(testPending()) - reporter.onSuiteEnd(suiteEnd()) - reporter.onRunnerEnd(runnerEnd()) - - const { results } = getResults(outputDir) - - expect(results).toHaveLength(1) - expect(results[0].steps).toHaveLength(0) - }) - - it('should not end step if it was not started', () => { - const allureOptions = { - stdout: true, - outputDir - } - const reporter = new AllureReporter(allureOptions) - reporter.onRunnerStart(Object.assign(runnerStart(), { isMultiremote: true })) - reporter.onSuiteStart(suiteStart()) - reporter.onTestStart(testStart()) - reporter.onAfterCommand(commandEnd(protocol === 'devtools')) - reporter.onTestSkip(testPending()) - reporter.onSuiteEnd(suiteEnd()) - reporter.onRunnerEnd(runnerEnd()) - - const { results } = getResults(outputDir) - - expect(results).toHaveLength(1) - expect(results[0].steps).toHaveLength(0) - }) - - it('should not add step if disableWebdriverStepsReporting = true', () => { - const allureOptions = { - stdout: true, - outputDir, - disableWebdriverStepsReporting: true - } - const reporter = new AllureReporter(allureOptions) - reporter.onRunnerStart(Object.assign(runnerStart(),)) - reporter.onSuiteStart(suiteStart()) - reporter.onTestStart(testStart()) - reporter.onBeforeCommand(commandStart(protocol === 'devtools')) - reporter.onAfterCommand(commandEnd(protocol === 'devtools')) - reporter.onTestSkip(testPending()) - reporter.onSuiteEnd(suiteEnd()) - reporter.onRunnerEnd(runnerEnd()) - - const { results } = getResults(outputDir) - - expect(results).toHaveLength(1) - expect(results[0].steps).toHaveLength(0) - }) - - it('should add step from command', () => { - const allureOptions = { - stdout: true, - outputDir, - } - const reporter = new AllureReporter(allureOptions) - - reporter.onRunnerStart(runnerStart()) - reporter.onSuiteStart(suiteStart()) - reporter.onTestStart(testStart()) - reporter.onBeforeCommand(commandStart(protocol === 'devtools')) - reporter.onAfterCommand(commandEnd(protocol === 'devtools')) - reporter.onTestSkip(testPending()) - reporter.onSuiteEnd(suiteEnd()) - reporter.onRunnerEnd(runnerEnd()) - - const { results } = getResults(outputDir) - - expect(results).toHaveLength(1) - expect(results[0].steps).toHaveLength(1) - - const responseAttachments = results[0].steps[0].attachments.filter( - (attachment: Attachment) => attachment.name === 'Response' - ) - - expect(results[0].steps[0].name).toEqual(assertionResults[protocol].commandTitle) - expect(responseAttachments).toHaveLength(1) - expect(results[0].steps[0].status).toEqual(Status.PASSED) - }) - - it('should not empty attach for step from command', () => { - const allureOptions = { - stdout: true, - outputDir, - } - const reporter = new AllureReporter(allureOptions) - - reporter.onRunnerStart(runnerStart()) - reporter.onSuiteStart(suiteStart()) - reporter.onTestStart(testStart()) - - const command = commandStart(protocol === 'devtools') - - delete command.body - - reporter.onBeforeCommand(command) - reporter.onAfterCommand(commandEnd(protocol === 'devtools')) - reporter.onTestSkip(testPending()) - reporter.onSuiteEnd(suiteEnd()) - reporter.onRunnerEnd(runnerEnd()) - - const { results } = getResults(outputDir) - - expect(results).toHaveLength(1) - expect(results[0].steps).toHaveLength(1) - - const requestAttachments = results[0].steps[0].attachments.filter( - (attachment: Attachment) => attachment.name === 'Request' - ) - - expect(results[0].steps[0].name).toEqual(assertionResults[protocol].commandTitle) - expect(results[0].steps[0].status).toEqual(Status.PASSED) - expect(requestAttachments).toHaveLength(0) - }) - - it('should add step with screenshot command', () => { - const allureOptions = { - stdout: true, - outputDir, - } - const reporter = new AllureReporter(allureOptions) - - reporter.onRunnerStart(runnerStart()) - reporter.onSuiteStart(suiteStart()) - reporter.onTestStart(testStart()) - reporter.onBeforeCommand(commandStartScreenShot(protocol === 'devtools')) - reporter.onAfterCommand(commandEndScreenShot(protocol === 'devtools')) - reporter.onTestSkip(testPending()) - reporter.onSuiteEnd(suiteEnd()) - reporter.onRunnerEnd(runnerEnd()) - - const { results } = getResults(outputDir) - - expect(results).toHaveLength(1) - expect(results[0].steps).toHaveLength(1) - - const screenshotAttachments = results[0].steps[0].attachments.filter( - (attachment: Attachment) => attachment.name === 'Screenshot' - ) - - expect(results[0].steps[0].name).toEqual(assertionResults[protocol].screenshotTitle) - expect(results[0].steps[0].status).toEqual(Status.PASSED) - expect(screenshotAttachments).toHaveLength(1) - }) - - it('should add step with screenshot command when disableWebdriverStepsReporting=true', () => { - const allureOptions = { - stdout: true, - outputDir, - disableWebdriverStepsReporting: true - } - const reporter = new AllureReporter(allureOptions) - - reporter.onRunnerStart(runnerStart()) - reporter.onSuiteStart(suiteStart()) - reporter.onTestStart(testStart()) - reporter.onBeforeCommand(commandStartScreenShot(protocol === 'devtools')) - reporter.onAfterCommand(commandEndScreenShot(protocol === 'devtools')) - reporter.onTestSkip(testPending()) - reporter.onSuiteEnd(suiteEnd()) - reporter.onRunnerEnd(runnerEnd()) - - const { results } = getResults(outputDir) - - expect(results).toHaveLength(1) - - const screenshotAttachments = results[0].attachments.filter( - (attachment: Attachment) => attachment.name === 'Screenshot' - ) - expect(screenshotAttachments).toHaveLength(1) - }) - - it('should not add step with screenshot command when disableWebdriverScreenshotsReporting=true', () => { - const allureOptions = { - stdout: true, - outputDir, - disableWebdriverScreenshotsReporting: true - } - const reporter = new AllureReporter(allureOptions) - - reporter.onRunnerStart(runnerStart()) - reporter.onSuiteStart(suiteStart()) - reporter.onTestStart(testStart()) - reporter.onBeforeCommand(commandStartScreenShot(protocol === 'devtools')) - reporter.onAfterCommand(commandEndScreenShot(protocol === 'devtools')) - reporter.onTestSkip(testPending()) - reporter.onSuiteEnd(suiteEnd()) - reporter.onRunnerEnd(runnerEnd()) - - const { results } = getResults(outputDir) - - expect(results).toHaveLength(1) - expect(results[0].steps).toHaveLength(1) - - const screenshotAttachments = results[0].steps[0].attachments.filter( - (attachment: Attachment) => attachment.name === 'Screenshot' - ) - - expect(results[0].steps[0].name).toEqual(assertionResults[protocol].screenshotTitle) - expect(results[0].steps[0].status).toEqual(Status.PASSED) - expect(screenshotAttachments).toHaveLength(0) - }) - - it('should attach screenshot on hook failure', () => { - const allureOptions = { - stdout: true, - outputDir, - disableMochaHooks: true, - disableWebdriverStepsReporting: true - } - const reporter = new AllureReporter(allureOptions) - - reporter.onRunnerStart(runnerStart()) - reporter.onSuiteStart(suiteStart()) - reporter.onHookStart(hookStart()) - reporter.onTestStart(testStart()) - reporter.onBeforeCommand(commandStartScreenShot(protocol === 'devtools')) - reporter.onAfterCommand(commandEndScreenShot(protocol === 'devtools')) - reporter.onHookEnd(hookFailed()) - reporter.onTestPass() - reporter.onSuiteEnd(suiteEnd()) - reporter.onRunnerEnd(runnerEnd()) - - const { results } = getResults(outputDir) - expect(results).toHaveLength(2) - - const result = results.find( res => res.attachments.length === 1) - expect(result).toBeDefined() - - const screenshotAttachments = result.attachments.filter( - (attachment: Attachment) => attachment.name === 'Screenshot' - ) - - expect(screenshotAttachments).toHaveLength(1) - }) - - it('should attach console log for passing test', () => { - const allureOptions = { - stdout: true, - outputDir, - disableMochaHooks: true, - addConsoleLogs: true - } - const reporter = new AllureReporter(allureOptions) - - reporter.onRunnerStart(runnerStart()) - reporter.onSuiteStart(suiteStart()) - //this shouldn't be logged - log('Printing to console 1') - reporter.onTestStart(testStart()) - //this should be logged - log('Printing to console 2') - //this shouldn't be logged - log('Printing webdriver to console 2') - reporter.onTestPass() - reporter.onSuiteEnd(suiteEnd()) - reporter.onRunnerEnd(runnerEnd()) - - const { results } = getResults(outputDir) - - expect(results).toHaveLength(1) - - const consoleAttachments = results[0].attachments.filter( - (attachment: Attachment) => attachment.name === 'Console Logs' - ) - - expect(consoleAttachments).toHaveLength(1) - }) - - it('should attach console log for failing test', () => { - const allureOptions = { - stdout: true, - outputDir, - disableMochaHooks: true, - addConsoleLogs: true - } - const reporter = new AllureReporter(allureOptions) - - reporter.onRunnerStart(runnerStart()) - reporter.onSuiteStart(suiteStart()) - //this shouldn't be logged - log('Printing to console 1') - reporter.onTestStart(testStart()) - //this should be logged - log('Printing to console 2') - //this shouldn't be logged - log('Printing webdriver to console 2') - reporter.onTestFail(testFailed()) - reporter.onSuiteEnd(suiteEnd()) - reporter.onRunnerEnd(runnerEnd()) - - const { results } = getResults(outputDir) - - expect(results).toHaveLength(1) - - const consoleAttachments = results[0].attachments.filter( - (attachment: Attachment) => attachment.name === 'Console Logs' - ) - - expect(consoleAttachments).toHaveLength(1) - }) - - it('should attach console log for skipping test', () => { - const allureOptions = { - stdout: true, - outputDir, - disableMochaHooks: true, - addConsoleLogs: true - } - const reporter = new AllureReporter(allureOptions) - - reporter.onRunnerStart(runnerStart()) - reporter.onSuiteStart(suiteStart()) - //this shouldn't be logged - log('Printing to console 1') - reporter.onTestStart(testStart()) - //this should be logged - log('Printing to console 2') - //this shouldn't be logged - log('Printing webdriver to console 2') - reporter.onTestSkip(testFailed()) - reporter.onSuiteEnd(suiteEnd()) - reporter.onRunnerEnd(runnerEnd()) - - const { results } = getResults(outputDir) - const consoleAttachments = results[0].attachments.filter( - (attachment: Attachment) => attachment.name === 'Console Logs' - ) - - expect(results).toHaveLength(1) - expect(consoleAttachments).toHaveLength(1) - }) - - it('should not attach webdriver logs', () => { - const allureOptions = { - stdout: true, - outputDir, - disableMochaHooks: true, - addConsoleLogs: true - } - const reporter = new AllureReporter(allureOptions) - - reporter.onRunnerStart(runnerStart()) - reporter.onSuiteStart(suiteStart()) - //this shouldn't be logged - log('Printing to console 1') - reporter.onTestStart(testStart()) - //this shouldn't be logged - log('Printing mwebdriver to console 2') - reporter.onTestPass() - reporter.onSuiteEnd(suiteEnd()) - reporter.onRunnerEnd(runnerEnd()) - - const { results } = getResults(outputDir) - const consoleAttachments = results[0].attachments.filter( - (attachment: Attachment) => attachment.name === 'Console Logs' - ) - - expect(results).toHaveLength(1) - expect(consoleAttachments).toHaveLength(0) - }) + beforeEach(() => { + outputDir = temporaryDirectory() }) -} + + afterEach(() => { + clean(outputDir) + }) + + it('should not add step if no tests started', () => { + const allureOptions = { + stdout: true, + outputDir + } + const reporter = new AllureReporter(allureOptions) + + reporter.onRunnerStart(runnerStart()) + reporter.onSuiteStart(suiteStart()) + reporter.onBeforeCommand(commandStart(false)) + reporter.onAfterCommand(commandEnd(false)) + reporter.onTestSkip(testPending()) + reporter.onSuiteEnd(suiteEnd()) + reporter.onRunnerEnd(runnerEnd()) + + const { results } = getResults(outputDir) + + expect(results).toHaveLength(1) + expect(results[0].steps).toHaveLength(0) + }) + + it('should not add step if isMultiremote = true', () => { + const allureOptions = { + stdout: true, + outputDir + } + const reporter = new AllureReporter(allureOptions) + reporter.onRunnerStart(Object.assign(runnerStart(), { isMultiremote: true })) + reporter.onSuiteStart(suiteStart()) + reporter.onTestStart(testStart()) + reporter.onBeforeCommand(commandStart(false)) + reporter.onAfterCommand(commandEnd(false)) + reporter.onTestSkip(testPending()) + reporter.onSuiteEnd(suiteEnd()) + reporter.onRunnerEnd(runnerEnd()) + + const { results } = getResults(outputDir) + + expect(results).toHaveLength(1) + expect(results[0].steps).toHaveLength(0) + }) + + it('should not end step if it was not started', () => { + const allureOptions = { + stdout: true, + outputDir + } + const reporter = new AllureReporter(allureOptions) + reporter.onRunnerStart(Object.assign(runnerStart(), { isMultiremote: true })) + reporter.onSuiteStart(suiteStart()) + reporter.onTestStart(testStart()) + reporter.onAfterCommand(commandEnd(false)) + reporter.onTestSkip(testPending()) + reporter.onSuiteEnd(suiteEnd()) + reporter.onRunnerEnd(runnerEnd()) + + const { results } = getResults(outputDir) + + expect(results).toHaveLength(1) + expect(results[0].steps).toHaveLength(0) + }) + + it('should not add step if disableWebdriverStepsReporting = true', () => { + const allureOptions = { + stdout: true, + outputDir, + disableWebdriverStepsReporting: true + } + const reporter = new AllureReporter(allureOptions) + reporter.onRunnerStart(Object.assign(runnerStart(),)) + reporter.onSuiteStart(suiteStart()) + reporter.onTestStart(testStart()) + reporter.onBeforeCommand(commandStart(false)) + reporter.onAfterCommand(commandEnd(false)) + reporter.onTestSkip(testPending()) + reporter.onSuiteEnd(suiteEnd()) + reporter.onRunnerEnd(runnerEnd()) + + const { results } = getResults(outputDir) + + expect(results).toHaveLength(1) + expect(results[0].steps).toHaveLength(0) + }) + + it('should add step from command', () => { + const allureOptions = { + stdout: true, + outputDir, + } + const reporter = new AllureReporter(allureOptions) + + reporter.onRunnerStart(runnerStart()) + reporter.onSuiteStart(suiteStart()) + reporter.onTestStart(testStart()) + reporter.onBeforeCommand(commandStart(false)) + reporter.onAfterCommand(commandEnd(false)) + reporter.onTestSkip(testPending()) + reporter.onSuiteEnd(suiteEnd()) + reporter.onRunnerEnd(runnerEnd()) + + const { results } = getResults(outputDir) + + expect(results).toHaveLength(1) + expect(results[0].steps).toHaveLength(1) + + const responseAttachments = results[0].steps[0].attachments.filter( + (attachment: Attachment) => attachment.name === 'Response' + ) + + expect(results[0].steps[0].name).toEqual(assertionResults['webdriver'].commandTitle) + expect(responseAttachments).toHaveLength(1) + expect(results[0].steps[0].status).toEqual(Status.PASSED) + }) + + it('should not empty attach for step from command', () => { + const allureOptions = { + stdout: true, + outputDir, + } + const reporter = new AllureReporter(allureOptions) + + reporter.onRunnerStart(runnerStart()) + reporter.onSuiteStart(suiteStart()) + reporter.onTestStart(testStart()) + + const command = commandStart(false) + + delete command.body + + reporter.onBeforeCommand(command) + reporter.onAfterCommand(commandEnd(false)) + reporter.onTestSkip(testPending()) + reporter.onSuiteEnd(suiteEnd()) + reporter.onRunnerEnd(runnerEnd()) + + const { results } = getResults(outputDir) + + expect(results).toHaveLength(1) + expect(results[0].steps).toHaveLength(1) + + const requestAttachments = results[0].steps[0].attachments.filter( + (attachment: Attachment) => attachment.name === 'Request' + ) + + expect(results[0].steps[0].name).toEqual(assertionResults['webdriver'].commandTitle) + expect(results[0].steps[0].status).toEqual(Status.PASSED) + expect(requestAttachments).toHaveLength(0) + }) + + it('should add step with screenshot command', () => { + const allureOptions = { + stdout: true, + outputDir, + } + const reporter = new AllureReporter(allureOptions) + + reporter.onRunnerStart(runnerStart()) + reporter.onSuiteStart(suiteStart()) + reporter.onTestStart(testStart()) + reporter.onBeforeCommand(commandStartScreenShot(false)) + reporter.onAfterCommand(commandEndScreenShot(false)) + reporter.onTestSkip(testPending()) + reporter.onSuiteEnd(suiteEnd()) + reporter.onRunnerEnd(runnerEnd()) + + const { results } = getResults(outputDir) + + expect(results).toHaveLength(1) + expect(results[0].steps).toHaveLength(1) + + const screenshotAttachments = results[0].steps[0].attachments.filter( + (attachment: Attachment) => attachment.name === 'Screenshot' + ) + + expect(results[0].steps[0].name).toEqual(assertionResults['webdriver'].screenshotTitle) + expect(results[0].steps[0].status).toEqual(Status.PASSED) + expect(screenshotAttachments).toHaveLength(1) + }) + + it('should add step with screenshot command when disableWebdriverStepsReporting=true', () => { + const allureOptions = { + stdout: true, + outputDir, + disableWebdriverStepsReporting: true + } + const reporter = new AllureReporter(allureOptions) + + reporter.onRunnerStart(runnerStart()) + reporter.onSuiteStart(suiteStart()) + reporter.onTestStart(testStart()) + reporter.onBeforeCommand(commandStartScreenShot(false)) + reporter.onAfterCommand(commandEndScreenShot(false)) + reporter.onTestSkip(testPending()) + reporter.onSuiteEnd(suiteEnd()) + reporter.onRunnerEnd(runnerEnd()) + + const { results } = getResults(outputDir) + + expect(results).toHaveLength(1) + + const screenshotAttachments = results[0].attachments.filter( + (attachment: Attachment) => attachment.name === 'Screenshot' + ) + expect(screenshotAttachments).toHaveLength(1) + }) + + it('should not add step with screenshot command when disableWebdriverScreenshotsReporting=true', () => { + const allureOptions = { + stdout: true, + outputDir, + disableWebdriverScreenshotsReporting: true + } + const reporter = new AllureReporter(allureOptions) + + reporter.onRunnerStart(runnerStart()) + reporter.onSuiteStart(suiteStart()) + reporter.onTestStart(testStart()) + reporter.onBeforeCommand(commandStartScreenShot(false)) + reporter.onAfterCommand(commandEndScreenShot(false)) + reporter.onTestSkip(testPending()) + reporter.onSuiteEnd(suiteEnd()) + reporter.onRunnerEnd(runnerEnd()) + + const { results } = getResults(outputDir) + + expect(results).toHaveLength(1) + expect(results[0].steps).toHaveLength(1) + + const screenshotAttachments = results[0].steps[0].attachments.filter( + (attachment: Attachment) => attachment.name === 'Screenshot' + ) + + expect(results[0].steps[0].name).toEqual(assertionResults['webdriver'].screenshotTitle) + expect(results[0].steps[0].status).toEqual(Status.PASSED) + expect(screenshotAttachments).toHaveLength(0) + }) + + it('should attach screenshot on hook failure', () => { + const allureOptions = { + stdout: true, + outputDir, + disableMochaHooks: true, + disableWebdriverStepsReporting: true + } + const reporter = new AllureReporter(allureOptions) + + reporter.onRunnerStart(runnerStart()) + reporter.onSuiteStart(suiteStart()) + reporter.onHookStart(hookStart()) + reporter.onTestStart(testStart()) + reporter.onBeforeCommand(commandStartScreenShot(false)) + reporter.onAfterCommand(commandEndScreenShot(false)) + reporter.onHookEnd(hookFailed()) + reporter.onTestPass() + reporter.onSuiteEnd(suiteEnd()) + reporter.onRunnerEnd(runnerEnd()) + + const { results } = getResults(outputDir) + expect(results).toHaveLength(2) + + const result = results.find((res: any) => res.attachments.length === 1) + expect(result).toBeDefined() + + const screenshotAttachments = result.attachments.filter( + (attachment: Attachment) => attachment.name === 'Screenshot' + ) + + expect(screenshotAttachments).toHaveLength(1) + }) + + it('should attach console log for passing test', () => { + const allureOptions = { + stdout: true, + outputDir, + disableMochaHooks: true, + addConsoleLogs: true + } + const reporter = new AllureReporter(allureOptions) + + reporter.onRunnerStart(runnerStart()) + reporter.onSuiteStart(suiteStart()) + //this shouldn't be logged + log('Printing to console 1') + reporter.onTestStart(testStart()) + //this should be logged + log('Printing to console 2') + //this shouldn't be logged + log('Printing webdriver to console 2') + reporter.onTestPass() + reporter.onSuiteEnd(suiteEnd()) + reporter.onRunnerEnd(runnerEnd()) + + const { results } = getResults(outputDir) + + expect(results).toHaveLength(1) + + const consoleAttachments = results[0].attachments.filter( + (attachment: Attachment) => attachment.name === 'Console Logs' + ) + + expect(consoleAttachments).toHaveLength(1) + }) + + it('should attach console log for failing test', () => { + const allureOptions = { + stdout: true, + outputDir, + disableMochaHooks: true, + addConsoleLogs: true + } + const reporter = new AllureReporter(allureOptions) + + reporter.onRunnerStart(runnerStart()) + reporter.onSuiteStart(suiteStart()) + //this shouldn't be logged + log('Printing to console 1') + reporter.onTestStart(testStart()) + //this should be logged + log('Printing to console 2') + //this shouldn't be logged + log('Printing webdriver to console 2') + reporter.onTestFail(testFailed()) + reporter.onSuiteEnd(suiteEnd()) + reporter.onRunnerEnd(runnerEnd()) + + const { results } = getResults(outputDir) + + expect(results).toHaveLength(1) + + const consoleAttachments = results[0].attachments.filter( + (attachment: Attachment) => attachment.name === 'Console Logs' + ) + + expect(consoleAttachments).toHaveLength(1) + }) + + it('should attach console log for skipping test', () => { + const allureOptions = { + stdout: true, + outputDir, + disableMochaHooks: true, + addConsoleLogs: true + } + const reporter = new AllureReporter(allureOptions) + + reporter.onRunnerStart(runnerStart()) + reporter.onSuiteStart(suiteStart()) + //this shouldn't be logged + log('Printing to console 1') + reporter.onTestStart(testStart()) + //this should be logged + log('Printing to console 2') + //this shouldn't be logged + log('Printing webdriver to console 2') + reporter.onTestSkip(testFailed()) + reporter.onSuiteEnd(suiteEnd()) + reporter.onRunnerEnd(runnerEnd()) + + const { results } = getResults(outputDir) + const consoleAttachments = results[0].attachments.filter( + (attachment: Attachment) => attachment.name === 'Console Logs' + ) + + expect(results).toHaveLength(1) + expect(consoleAttachments).toHaveLength(1) + }) + + it('should not attach webdriver logs', () => { + const allureOptions = { + stdout: true, + outputDir, + disableMochaHooks: true, + addConsoleLogs: true + } + const reporter = new AllureReporter(allureOptions) + + reporter.onRunnerStart(runnerStart()) + reporter.onSuiteStart(suiteStart()) + //this shouldn't be logged + log('Printing to console 1') + reporter.onTestStart(testStart()) + //this shouldn't be logged + log('Printing mwebdriver to console 2') + reporter.onTestPass() + reporter.onSuiteEnd(suiteEnd()) + reporter.onRunnerEnd(runnerEnd()) + + const { results } = getResults(outputDir) + const consoleAttachments = results[0].attachments.filter( + (attachment: Attachment) => attachment.name === 'Console Logs' + ) + + expect(results).toHaveLength(1) + expect(consoleAttachments).toHaveLength(0) + }) +}) diff --git a/packages/wdio-browser-runner/src/vite/plugins/testrunner.ts b/packages/wdio-browser-runner/src/vite/plugins/testrunner.ts index ebbfe9549..759e047bd 100644 --- a/packages/wdio-browser-runner/src/vite/plugins/testrunner.ts +++ b/packages/wdio-browser-runner/src/vite/plugins/testrunner.ts @@ -41,7 +41,7 @@ const resolvedVirtualModuleId = '\0' + virtualModuleId * functionality */ const MODULES_TO_MOCK = [ - 'import-meta-resolve', 'puppeteer-core', 'archiver', 'glob', 'devtools', 'ws', 'decamelize', + 'import-meta-resolve', 'puppeteer-core', 'archiver', 'glob', 'ws', 'decamelize', 'geckodriver', 'safaridriver', 'edgedriver', '@puppeteer/browsers', 'locate-app', 'wait-port', 'lodash.isequal', '@wdio/repl' ] diff --git a/packages/wdio-cli/src/utils.ts b/packages/wdio-cli/src/utils.ts index b3856696e..342689954 100644 --- a/packages/wdio-cli/src/utils.ts +++ b/packages/wdio-cli/src/utils.ts @@ -635,7 +635,7 @@ export function specifyVersionIfNeeded(packagesToInstall: string[], version: str return packagesToInstall.map((p) => { if ( (p.startsWith('@wdio') && p !== '@wdio/visual-service') || - ['devtools', 'webdriver', 'webdriverio'].includes(p) + ['webdriver', 'webdriverio'].includes(p) ) { const tag = major && npmTag === 'latest' ? `^${major}.${minor}.${patch}-${tagName}.${build}` diff --git a/packages/wdio-devtools-service/tests/utils.test.ts b/packages/wdio-devtools-service/tests/utils.test.ts index 5d884acfa..e4b650d25 100644 --- a/packages/wdio-devtools-service/tests/utils.test.ts +++ b/packages/wdio-devtools-service/tests/utils.test.ts @@ -60,7 +60,13 @@ describe('getLighthouseDriver', () => { const session = { connection: vi.fn().mockReturnValue({ url: urlMock }) } - const target = { _targetId: 'foobar321' } + const target = { + asPage: vi.fn().mockReturnValue({ + mainFrame: vi.fn().mockReturnValue({ + _id: 'foobar321' + }) + }) + } const driver = await getLighthouseDriver(session as any, target as any) // @ts-expect-error driver._connection.sendCommand('foobar') diff --git a/packages/wdio-types/src/Automation.ts b/packages/wdio-types/src/Automation.ts new file mode 100644 index 000000000..5dd665e64 --- /dev/null +++ b/packages/wdio-types/src/Automation.ts @@ -0,0 +1,20 @@ +export interface Driver { + newSession( + options: T, + modifier?: (...args: any[]) => any, + userPrototype?: Record, + customCommandWrapper?: (...args: any[]) => any + ): any + + attachToSession( + options: any, + modifier?: (...args: any[]) => any, + userPrototype?: Record, + customCommandWrapper?: (...args: any[]) => any + ): any + + reloadSession ( + client: any, + newCapabilities: WebdriverIO.Capabilities + ): any +} diff --git a/packages/wdio-types/src/Capabilities.ts b/packages/wdio-types/src/Capabilities.ts index 044a43d20..0be1416bb 100644 --- a/packages/wdio-types/src/Capabilities.ts +++ b/packages/wdio-types/src/Capabilities.ts @@ -151,7 +151,7 @@ export interface DesiredCapabilities extends WebdriverIO.Capabilities, SauceLabs excludeDriverLogs?: string[] } -export interface VendorExtensions extends EdgeCapabilities, AppiumCapabilities, WebdriverIO.WDIODevtoolsOptions, WebdriverIOCapabilities, WebdriverIO.WDIOVSCodeServiceOptions { +export interface VendorExtensions extends EdgeCapabilities, AppiumCapabilities, WebdriverIOCapabilities, WebdriverIO.WDIOVSCodeServiceOptions { // Aerokube Selenoid specific 'selenoid:options'?: SelenoidOptions // Aerokube Moon specific diff --git a/packages/wdio-types/src/Options.ts b/packages/wdio-types/src/Options.ts index 451343ce8..57d897917 100644 --- a/packages/wdio-types/src/Options.ts +++ b/packages/wdio-types/src/Options.ts @@ -3,7 +3,7 @@ import type { Hooks, ServiceEntry } from './Services.js' import type { ReporterEntry } from './Reporters.js' export type WebDriverLogTypes = 'trace' | 'debug' | 'info' | 'warn' | 'error' | 'silent' -export type SupportedProtocols = 'webdriver' | 'devtools' | './protocol-stub.js' +export type SupportedProtocols = 'webdriver' | './protocol-stub.js' export type Method = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'HEAD' | 'DELETE' | 'OPTIONS' | 'TRACE' | 'get' | 'post' | 'put' | 'patch' | 'head' | 'delete' | 'options' | 'trace' @@ -86,7 +86,7 @@ export interface WebDriver extends Connection { * * @example * ```js - * // WebDriver/DevTools session + * // WebDriver session * const browser = remote({ * capabilities: { * browserName: 'chrome', diff --git a/packages/wdio-types/src/index.ts b/packages/wdio-types/src/index.ts index af40a9f4f..9a1e7a65e 100644 --- a/packages/wdio-types/src/index.ts +++ b/packages/wdio-types/src/index.ts @@ -1,3 +1,4 @@ +import type * as Automation from './Automation.js' import type * as Capabilities from './Capabilities.js' import type * as Clients from './Clients.js' import type * as Options from './Options.js' @@ -11,7 +12,7 @@ import type * as Workers from './Workers.js' */ export { MESSAGE_TYPES } from './Workers.js' -export type { Capabilities, Clients, Options, Services, Frameworks, Reporters, Workers } +export type { Automation, Capabilities, Clients, Options, Services, Frameworks, Reporters, Workers } export type JsonPrimitive = string | number | boolean | null export type JsonObject = { [x: string]: JsonPrimitive | JsonObject | JsonArray } @@ -46,7 +47,6 @@ declare global { interface ReporterOption extends Reporters.Options {} interface Config extends Options.Testrunner {} interface HookFunctionExtension {} - interface WDIODevtoolsOptions {} interface WDIOVSCodeServiceOptions {} interface BrowserRunnerOptions {} interface ChromedriverOptions extends DriverOptions {} diff --git a/packages/wdio-utils/src/envDetector.ts b/packages/wdio-utils/src/envDetector.ts index 1f7d081f1..f88b4cfee 100644 --- a/packages/wdio-utils/src/envDetector.ts +++ b/packages/wdio-utils/src/envDetector.ts @@ -225,13 +225,18 @@ function isSeleniumStandalone(capabilities?: Capabilities.DesiredCapabilities) { /** * returns information about the environment before the session is created * @param {Object} capabilities caps provided by user - * @param {string=} automationProtocol `devtools` * @return {Object} object with environment flags */ -export function capabilitiesEnvironmentDetector(capabilities: WebdriverIO.Capabilities, automationProtocol: string) { - return automationProtocol === 'devtools' - ? devtoolsEnvironmentDetector(capabilities) - : webdriverEnvironmentDetector(capabilities) +export function capabilitiesEnvironmentDetector(capabilities: WebdriverIO.Capabilities) { + return { + isChrome: isChrome(capabilities), + isFirefox: isFirefox(capabilities), + isMobile: isMobile(capabilities), + isIOS: isIOS(capabilities), + isAndroid: isAndroid(capabilities), + isSauce: isSauce(capabilities), + isBidi: isBidi(capabilities) + } } /** @@ -257,41 +262,3 @@ export function sessionEnvironmentDetector({ capabilities, requestedCapabilities isBidi: isBidi(capabilities) } } - -/** - * returns information about the environment when `devtools` protocol is used - * @param {Object} capabilities caps of session response - * @return {Object} object with environment flags - */ -export function devtoolsEnvironmentDetector({ browserName }: WebdriverIO.Capabilities) { - return { - isDevTools: true, - isW3C: true, - isMobile: false, - isIOS: false, - isAndroid: false, - isFirefox: false, - isChrome: browserName === 'chrome', - isSauce: false, - isSeleniumStandalone: false, - isBidi: false - } -} - -/** - * returns information about the environment before the session is created - * `isW3C`, `isSeleniumStandalone` cannot be detected - * @param {Object} capabilities caps provided by user - * @return {Object} object with environment flags - */ -export function webdriverEnvironmentDetector(capabilities: WebdriverIO.Capabilities) { - return { - isChrome: isChrome(capabilities), - isFirefox: isFirefox(capabilities), - isMobile: isMobile(capabilities), - isIOS: isIOS(capabilities), - isAndroid: isAndroid(capabilities), - isSauce: isSauce(capabilities), - isBidi: isBidi(capabilities) - } -} diff --git a/packages/wdio-utils/src/index.ts b/packages/wdio-utils/src/index.ts index 2aa8f1830..985e2ebf8 100644 --- a/packages/wdio-utils/src/index.ts +++ b/packages/wdio-utils/src/index.ts @@ -12,10 +12,7 @@ import { import { wrapCommand, executeHooksWithArgs, executeAsync } from './shim.js' import * as asyncIterators from './pIteration.js' import { testFnWrapper, wrapGlobalTestMethod } from './test-framework/index.js' -import { - isW3C, capabilitiesEnvironmentDetector, - sessionEnvironmentDetector, devtoolsEnvironmentDetector -} from './envDetector.js' +import { isW3C, capabilitiesEnvironmentDetector, sessionEnvironmentDetector } from './envDetector.js' import { UNICODE_CHARACTERS, HOOK_DEFINITION } from './constants.js' export { @@ -50,7 +47,6 @@ export { isW3C, sessionEnvironmentDetector, capabilitiesEnvironmentDetector, - devtoolsEnvironmentDetector, /** * constants diff --git a/packages/wdio-utils/src/node/manager.ts b/packages/wdio-utils/src/node/manager.ts index ba9c6cb71..d85c33322 100644 --- a/packages/wdio-utils/src/node/manager.ts +++ b/packages/wdio-utils/src/node/manager.ts @@ -30,12 +30,7 @@ function mapCapabilities ( const isMultiremote = Boolean(multiremoteCaps[Object.keys(cap)[0]].capabilities) if (isMultiremote) { - return Object.values(multiremoteCaps).map((c) => { - if (c.automationProtocol === 'devtools') { - return - } - return c.capabilities - }) as WebdriverIO.Capabilities[] + return Object.values(multiremoteCaps).map((c) => c.capabilities) as WebdriverIO.Capabilities[] } else if (w3cCaps.alwaysMatch) { return w3cCaps.alwaysMatch } @@ -43,9 +38,6 @@ function mapCapabilities ( }).flat() : Object.values(caps as Capabilities.MultiRemoteCapabilities).map((mrOpts) => { const w3cCaps = mrOpts.capabilities as Capabilities.W3CCapabilities - if (mrOpts.automationProtocol === 'devtools') { - return - } if (w3cCaps.alwaysMatch) { return w3cCaps.alwaysMatch } @@ -55,7 +47,7 @@ function mapCapabilities ( /** * only set up driver if */ - // - capabilities are defined and not empty because automationProtocol is set to `devtools` + // - capabilities are defined and not empty cap && // - browserName is defined so we know it is a browser session cap.browserName && @@ -64,9 +56,7 @@ function mapCapabilities ( // - we are not running Safari (driver already installed on macOS) !isSafari(cap.browserName) && // - driver options don't define a binary path - !getDriverOptions(cap).binary && - // - user is not defining "devtools" as automation protocol - options.automationProtocol !== 'devtools' + !getDriverOptions(cap).binary )) as WebdriverIO.Capabilities[] /** diff --git a/packages/wdio-utils/tests/envDetector.test.ts b/packages/wdio-utils/tests/envDetector.test.ts index bf60727cd..69cf62c57 100644 --- a/packages/wdio-utils/tests/envDetector.test.ts +++ b/packages/wdio-utils/tests/envDetector.test.ts @@ -298,18 +298,9 @@ describe('sessionEnvironmentDetector', () => { describe('capabilitiesEnvironmentDetector', () => { it('should return env flags without isW3C and isSeleniumStandalone', () => { const sessionFlags = sessionEnvironmentDetector({ capabilities: {}, requestedCapabilities: { browserName: '' } }) - const capabilitiesFlags = capabilitiesEnvironmentDetector({}, 'webdriver') + const capabilitiesFlags = capabilitiesEnvironmentDetector({}) const expectedFlags = Object.keys(sessionFlags).filter(flagName => !['isW3C', 'isSeleniumStandalone'].includes(flagName)) expect(Object.keys(capabilitiesFlags)).toEqual(expectedFlags) }) - - it('should return devtools env flags if automationProtocol is devtools', () => { - const capabilitiesFlags = capabilitiesEnvironmentDetector({}, 'devtools') - - expect((capabilitiesFlags as any).isDevTools).toBe(true) - expect((capabilitiesFlags as any).isSeleniumStandalone).toBe(false) - expect(capabilitiesFlags.isChrome).toBe(false) - expect(capabilitiesFlags.isMobile).toBe(false) - }) }) diff --git a/packages/wdio-utils/tests/node/index.test.ts b/packages/wdio-utils/tests/node/index.test.ts index 1b75ede0c..90708f76a 100644 --- a/packages/wdio-utils/tests/node/index.test.ts +++ b/packages/wdio-utils/tests/node/index.test.ts @@ -47,7 +47,6 @@ vi.mock('node:child_process', () => ({ })) vi.mock('@wdio/logger', () => import(path.join(process.cwd(), '__mocks__', '@wdio/logger'))) -vi.mock('devtools', () => ({ default: 'devtools package' })) vi.mock('webdriver', () => ({ default: 'webdriver package' })) vi.mock('safaridriver', () => ({ start: vi.fn().mockReturnValue({ diff --git a/packages/wdio-utils/tests/node/manager.test.ts b/packages/wdio-utils/tests/node/manager.test.ts index 379a3f7b2..cb71707db 100644 --- a/packages/wdio-utils/tests/node/manager.test.ts +++ b/packages/wdio-utils/tests/node/manager.test.ts @@ -90,24 +90,6 @@ describe('setupDriver', () => { expect(setupGeckodriver).toBeCalledWith('/foo/bar', '6') }) - test('no setup with testrunner capabilities when automation protocol is set to devtools', async () => { - await setupDriver({ - automationProtocol: 'devtools' - } as any, [{ - browserName: 'chrome', - browserVersion: '1' - }, { - browserName: 'firefox', - browserVersion: '5' - }, { - browserName: 'edge', - browserVersion: '4' - }]) - expect(setupGeckodriver).toBeCalledTimes(0) - expect(setupChromedriver).toBeCalledTimes(0) - expect(setupEdgedriver).toBeCalledTimes(0) - }) - test('with multiremote capabilities', async () => { await setupDriver({}, { browserA: { @@ -203,26 +185,6 @@ describe('setupDriver', () => { expect(setupGeckodriver).toBeCalledWith('/foo/bar', '7') }) - test('no setup with multiremote capabilities when automation protocol is set to devtools', async () => { - await setupDriver({}, { - browserA: { - capabilities: { - browserName: 'chrome', - browserVersion: '1' - } - }, - browserB: { - automationProtocol: 'devtools', - capabilities: { - browserName: 'edge', - browserVersion: '3' - } - } - }) - expect(setupChromedriver).toBeCalledTimes(1) - expect(setupEdgedriver).toBeCalledTimes(0) - }) - test('with multiremote capabilities series', async () => { await setupDriver({}, [{ browserA: { @@ -313,25 +275,6 @@ describe('setupDriver', () => { expect(setupGeckodriver).toBeCalledWith('/foo/bar', '5') expect(setupGeckodriver).toBeCalledWith('/foo/bar', '6') }) - - test('no setup with multiremote capabilities series when automation protocol is set to devtools', async () => { - await setupDriver({}, [{ - browserA: { - capabilities: { - browserName: 'chrome', - browserVersion: '1' - } - }, - browserB: { - automationProtocol: 'devtools', - capabilities: { - browserName: 'chrome', - browserVersion: '2' - } - } - }]) - expect(setupChromedriver).toBeCalledTimes(1) - }) }) describe('setupBrowser', () => { diff --git a/packages/webdriver/src/types.ts b/packages/webdriver/src/types.ts index 121afda82..6479b19a9 100644 --- a/packages/webdriver/src/types.ts +++ b/packages/webdriver/src/types.ts @@ -20,7 +20,6 @@ export interface SessionFlags { isIOS: boolean isSauce: boolean isSeleniumStandalone: boolean - isDevTools: boolean isBidi: boolean } diff --git a/packages/webdriver/tests/command.test.ts b/packages/webdriver/tests/command.test.ts index a66537abe..dbc45a938 100644 --- a/packages/webdriver/tests/command.test.ts +++ b/packages/webdriver/tests/command.test.ts @@ -70,7 +70,6 @@ class FakeClient extends EventEmitter { isIOS = false isSauce = false isFirefox = false - isDevTools = false isBidi = false isSeleniumStandalone = false sessionId = '123' diff --git a/packages/webdriverio/README.md b/packages/webdriverio/README.md index 21f6f43ff..d2722fcba 100644 --- a/packages/webdriverio/README.md +++ b/packages/webdriverio/README.md @@ -3,7 +3,7 @@ WebdriverIO > Next-gen browser and mobile automation test framework for Node.js -This package provides an easy-to-manage API and a lot of syntactical sugar on top of the WebDriver specification. You can use WebdriverIO as a standalone package or via a test runner using [`@wdio/cli`](https://webdriver.io/docs/clioptions). WebdriverIO allows you to run tests locally using the WebDriver or Chrome DevTools protocol as well as remote user agents using cloud providers like [Sauce Labs](https://saucelabs.com/). +This package provides an easy-to-manage API and a lot of syntactical sugar on top of the WebDriver specification. You can use WebdriverIO as a standalone package or via a test runner using [`@wdio/cli`](https://webdriver.io/docs/clioptions). WebdriverIO allows you to run tests locally using the WebDriver as well as remote user agents using cloud providers like [Sauce Labs](https://saucelabs.com/). ## Installation diff --git a/packages/webdriverio/package.json b/packages/webdriverio/package.json index 7a8d07162..84b14d623 100644 --- a/packages/webdriverio/package.json +++ b/packages/webdriverio/package.json @@ -36,7 +36,6 @@ "mocha", "nodeUnit", "buster", - "devtools", "puppeteer", "vows", "jasmine", @@ -92,13 +91,9 @@ "webdriver": "8.32.0" }, "peerDependencies": { - "devtools": "^8.14.0", "puppeteer-core": "^22.3.0" }, "peerDependenciesMeta": { - "devtools": { - "optional": true - }, "puppeteer-core": { "optional": true } diff --git a/packages/webdriverio/src/commands/browser/keys.ts b/packages/webdriverio/src/commands/browser/keys.ts index b8c23d611..b486c6de3 100644 --- a/packages/webdriverio/src/commands/browser/keys.ts +++ b/packages/webdriverio/src/commands/browser/keys.ts @@ -33,11 +33,11 @@ export async function keys ( * replace key with corresponding unicode character */ if (typeof value === 'string') { - keySequence = checkUnicode(value as keyof typeof UNICODE_CHARACTERS, this.isDevTools) + keySequence = checkUnicode(value as keyof typeof UNICODE_CHARACTERS) } else if (Array.isArray(value)) { const charArray: (keyof typeof UNICODE_CHARACTERS)[] = value as any for (const charSet of charArray) { - keySequence = keySequence.concat(checkUnicode(charSet, this.isDevTools)) + keySequence = keySequence.concat(checkUnicode(charSet)) } } else { throw new Error('"keys" command requires a string or array of strings as parameter') diff --git a/packages/webdriverio/src/constants.ts b/packages/webdriverio/src/constants.ts index c5c35465c..f5a7f52a8 100644 --- a/packages/webdriverio/src/constants.ts +++ b/packages/webdriverio/src/constants.ts @@ -1,13 +1,8 @@ -import { fileURLToPath } from 'node:url' -import { dirname, resolve } from 'node:path' -import { createRequire } from 'node:module' - import { UNICODE_CHARACTERS, HOOK_DEFINITION } from '@wdio/utils' import type { Options, Capabilities } from '@wdio/types' export enum SupportedAutomationProtocols { webdriver = 'webdriver', - devtools = 'devtools', stub = './protocol-stub.js' } @@ -29,21 +24,6 @@ export const WDIO_DEFAULTS: Options.Definition = { if (!Object.values(SupportedAutomationProtocols).includes(param.toLowerCase() as SupportedAutomationProtocols)) { throw new Error(`Currently only "webdriver" and "devtools" is supported as automationProtocol, you set "${param}"`) } - - try { - const __dirname = dirname(fileURLToPath(import.meta.url)) - const require = createRequire(import.meta.url) - const id = param === SupportedAutomationProtocols.stub - ? resolve(__dirname, '..', 'build', param) - : param - require.resolve(id) - } catch (err: any) { - /* istanbul ignore next */ - throw new Error( - 'Automation protocol package is not installed!\n' + - `Please install it via \`npm install ${param}\`` - ) - } } }, /** diff --git a/packages/webdriverio/src/protocol-stub.ts b/packages/webdriverio/src/protocol-stub.ts index 6ddb30fc9..fe3a7d30d 100644 --- a/packages/webdriverio/src/protocol-stub.ts +++ b/packages/webdriverio/src/protocol-stub.ts @@ -18,7 +18,7 @@ export default class ProtocolStub { customCommands: [], // internally used to transfer custom commands to the actual protocol instance overwrittenCommands: [], // internally used to transfer overwritten commands to the actual protocol instance commandList: [], - ...capabilitiesEnvironmentDetector(capabilities, (options as any)._automationProtocol || 'webdriver') + ...capabilitiesEnvironmentDetector(capabilities) } browser.addCommand = (...args: any) => browser.customCommands.push(args) @@ -27,11 +27,10 @@ export default class ProtocolStub { } /** - * added just in case user wants to somehow reload webdriver or devtools session - * before it was started. + * added just in case user wants to somehow reload webdriver before it was started. */ static reloadSession () { - throw new Error('Protocol Stub: Make sure to start webdriver or devtools session before reloading it.') + throw new Error('Protocol Stub: Make sure to start the session before reloading it.') } static attachToSession (options: never, modifier?: Function) { diff --git a/packages/webdriverio/src/utils/driver.ts b/packages/webdriverio/src/utils/driver.ts index 52052ce87..6cff02e13 100644 --- a/packages/webdriverio/src/utils/driver.ts +++ b/packages/webdriverio/src/utils/driver.ts @@ -1,48 +1,42 @@ -import WebDriver from 'webdriver' -import logger from '@wdio/logger' +import type { Automation } from '@wdio/types' import ProtocolStub from '../protocol-stub.js' import detectBackend from './detectBackend.js' import { SupportedAutomationProtocols } from '../constants.js' import type { RemoteOptions } from '../types.js' -const log = logger('webdriverio') +interface ProtocolDriver { + Driver: Automation.Driver + options: RemoteOptions +} -export async function getProtocolDriver (options: RemoteOptions) { +let webdriverImport: Automation.Driver | undefined + +/** + * get protocol driver + * @param {RemoteOptions} options remote options + * @return {Automation.Driver} automation driver + */ +export async function getProtocolDriver (options: RemoteOptions): Promise { + /** + * We still want to be able to inject a stub driver to avoid loading the actual + * driver package in two places: + * + * - when initiating the Mocha or Jasmine framework and allow to us to evaluate + * whether tests are being executed in the worker before starting a session + * - when running component tests where we don't need a driver at all + */ if (options.automationProtocol === SupportedAutomationProtocols.stub) { return { Driver: ProtocolStub, options } } /** - * return `devtools` if explicitly set + * update connection parameters if we are running in a cloud */ - if (options.automationProtocol === SupportedAutomationProtocols.devtools || options.automationProtocol?.startsWith('/@fs/')) { - try { - const DevTools = await import(options.automationProtocol) - log.info('Starting session using Chrome DevTools as automation protocol and Puppeteer as driver') - return { Driver: DevTools.default, options } - } catch (err: unknown) { - throw new Error( - `Failed to import "${options.automationProtocol}" as automation protocol driver!\n` + - (options.automationProtocol === SupportedAutomationProtocols.devtools - ? 'Make sure to have it installed as dependency (`npm i devtools`)!\n' - : '' - ) + - `Error: ${(err as Error).message}` - ) - } + if (typeof options.user === 'string' && typeof options.key === 'string') { + Object.assign(options, detectBackend(options)) } - if (options.automationProtocol === SupportedAutomationProtocols.webdriver) { - /** - * update connection parameters if we are running in a cloud - */ - if (typeof options.user === 'string' && typeof options.key === 'string') { - Object.assign(options, detectBackend(options)) - } - - return { Driver: WebDriver, options } - } - - throw new Error(`Undefined or unsupported automation protocol "${options.automationProtocol}"`) + const Driver = webdriverImport || (await import(options.automationProtocol || 'webdriver')).default + return { Driver, options } } diff --git a/packages/webdriverio/src/utils/index.ts b/packages/webdriverio/src/utils/index.ts index d53934ae8..d39f609a8 100644 --- a/packages/webdriverio/src/utils/index.ts +++ b/packages/webdriverio/src/utils/index.ts @@ -172,7 +172,7 @@ export function parseCSS (cssPropertyValue: string, cssProperty?: string) { * @param {string} value text * @return {Array} set of characters or unicode symbols */ -export function checkUnicode (value: string, isDevTools = false) { +export function checkUnicode (value: string) { /** * "Ctrl" key is specially handled based on OS in action class */ @@ -187,10 +187,6 @@ export function checkUnicode (value: string, isDevTools = false) { return new GraphemeSplitter().splitGraphemes(value) } - if (isDevTools) { - return [value] - } - return [UNICODE_CHARACTERS[value as keyof typeof UNICODE_CHARACTERS]] } @@ -246,7 +242,7 @@ export async function findElement( /** * check if shadow DOM integration is used */ - if (!this.isDevTools && typeof selector === 'string' && selector.startsWith(DEEP_SELECTOR)) { + if (typeof selector === 'string' && selector.startsWith(DEEP_SELECTOR)) { const notFoundError = new Error(`shadow selector "${selector.slice(DEEP_SELECTOR.length)}" did not return an HTMLElement`) let elem: ElementReference | ElementReference[] = await browserObject.execute( `return (${querySelectorAllDeep}).apply(null, arguments)`, @@ -332,7 +328,7 @@ export async function findElements( /** * check if shadow DOM integration is used */ - if (!this.isDevTools && typeof selector === 'string' && selector.startsWith(DEEP_SELECTOR)) { + if (typeof selector === 'string' && selector.startsWith(DEEP_SELECTOR)) { const elems: ElementReference | ElementReference[] = await browserObject.execute( `return (${querySelectorAllDeep}).apply(null, arguments)`, true, diff --git a/packages/webdriverio/tests/addCommand.test.ts b/packages/webdriverio/tests/addCommand.test.ts index 1e9e40f2c..ada57b7d5 100644 --- a/packages/webdriverio/tests/addCommand.test.ts +++ b/packages/webdriverio/tests/addCommand.test.ts @@ -5,7 +5,6 @@ import type { Options, Capabilities } from '@wdio/types' import { remote, multiremote } from '../src/index.js' vi.mock('fetch') -vi.mock('devtools') vi.mock('@wdio/logger', () => import(path.join(process.cwd(), '__mocks__', '@wdio/logger'))) const remoteConfig: Options.WebdriverIO = { diff --git a/packages/webdriverio/tests/commands/browser/mock.test.ts b/packages/webdriverio/tests/commands/browser/mock.test.ts index d566a6ab6..dcbadbb9a 100644 --- a/packages/webdriverio/tests/commands/browser/mock.test.ts +++ b/packages/webdriverio/tests/commands/browser/mock.test.ts @@ -4,7 +4,6 @@ import { expect, describe, it, beforeEach, vi } from 'vitest' import { remote } from '../../../src/index.js' vi.mock('fetch') -vi.mock('devtools') vi.mock('@wdio/logger', () => import(path.join(process.cwd(), '__mocks__', '@wdio/logger'))) vi.mock('@wdio/utils', async (origMod) => { const orig: any = await origMod() diff --git a/packages/webdriverio/tests/commands/browser/reloadSession.test.ts b/packages/webdriverio/tests/commands/browser/reloadSession.test.ts index f9ffcceb6..d5d1e6aa0 100644 --- a/packages/webdriverio/tests/commands/browser/reloadSession.test.ts +++ b/packages/webdriverio/tests/commands/browser/reloadSession.test.ts @@ -3,7 +3,6 @@ import { expect, describe, afterEach, it, vi } from 'vitest' import { remote } from '../../../src/index.js' vi.mock('fetch') -vi.mock('devtools') vi.mock('@wdio/logger', () => import(path.join(process.cwd(), '__mocks__', '@wdio/logger'))) vi.mock('@wdio/utils', async (origMod) => { const orig: any = await origMod() diff --git a/packages/webdriverio/tests/commands/browser/uploadFile.test.ts b/packages/webdriverio/tests/commands/browser/uploadFile.test.ts index 8c990307b..00ced7ed3 100644 --- a/packages/webdriverio/tests/commands/browser/uploadFile.test.ts +++ b/packages/webdriverio/tests/commands/browser/uploadFile.test.ts @@ -12,7 +12,6 @@ vi.mock('node:fs', () => ({ })) vi.mock('fetch') vi.mock('archiver') -vi.mock('devtools') vi.mock('@wdio/logger', () => import(path.join(process.cwd(), '__mocks__', '@wdio/logger'))) describe('uploadFile', () => { diff --git a/packages/webdriverio/tests/commands/element/isDisplayed.test.ts b/packages/webdriverio/tests/commands/element/isDisplayed.test.ts index b606aba10..ca91f6357 100644 --- a/packages/webdriverio/tests/commands/element/isDisplayed.test.ts +++ b/packages/webdriverio/tests/commands/element/isDisplayed.test.ts @@ -4,7 +4,6 @@ import { expect, describe, it, beforeEach, vi } from 'vitest' import { remote } from '../../../src/index.js' vi.mock('fetch') -vi.mock('devtools') vi.mock('@wdio/logger', () => import(path.join(process.cwd(), '__mocks__', '@wdio/logger'))) vi.mock('../../../src/scripts/isElementDisplayed', () => ({ diff --git a/packages/webdriverio/tests/constants.test.ts b/packages/webdriverio/tests/constants.test.ts index b758446c1..b6ce66f37 100644 --- a/packages/webdriverio/tests/constants.test.ts +++ b/packages/webdriverio/tests/constants.test.ts @@ -11,6 +11,5 @@ describe('WDIO_DEFAULTS', () => { expect(() => WDIO_DEFAULTS.automationProtocol!.validate!('foobar')).toThrow() WDIO_DEFAULTS.automationProtocol!.validate!('webdriver') - WDIO_DEFAULTS.automationProtocol!.validate!('devtools') }) }) diff --git a/packages/webdriverio/tests/index.test.ts b/packages/webdriverio/tests/index.test.ts index 2df572e0b..ea30ff726 100644 --- a/packages/webdriverio/tests/index.test.ts +++ b/packages/webdriverio/tests/index.test.ts @@ -5,7 +5,6 @@ import { logMock } from '@wdio/logger' import * as webdriverio from '../src/index.js' vi.mock('fetch') -vi.mock('devtools') vi.mock('@wdio/logger', () => import(path.join(process.cwd(), '__mocks__', '@wdio/logger'))) describe('index.js', () => { diff --git a/packages/webdriverio/tests/module.test.ts b/packages/webdriverio/tests/module.test.ts index ebc79a3ed..755e53909 100644 --- a/packages/webdriverio/tests/module.test.ts +++ b/packages/webdriverio/tests/module.test.ts @@ -39,29 +39,6 @@ vi.mock('webdriver', () => { } }) -vi.mock('devtools', () => { - const client = { sessionId: 'foobar-123', isDevTools: true } - const newSessionMock = vi.fn() - newSessionMock.mockReturnValue(new Promise((resolve) => resolve(client))) - newSessionMock.mockImplementation((params, cb) => { - const result = cb(client, params) - // @ts-ignore mock feature - if (params.test_multiremote) { - result.options = { logLevel: 'error' } - } - return result - }) - const attachToSessionMock = vi.fn().mockReturnValue(client) - return { - SUPPORTED_BROWSER: ['chrome'], - DEFAULTS: {}, - default: class DevtoolsMock { - static newSession = newSessionMock - static attachToSession = attachToSessionMock - } - } -}) - vi.mock('@wdio/config', () => { const validateConfigMock = { validateConfig: vi.fn((_, args) => args), diff --git a/packages/webdriverio/tests/multiremote.test.ts b/packages/webdriverio/tests/multiremote.test.ts index 9dc6e1122..3a0dcf747 100644 --- a/packages/webdriverio/tests/multiremote.test.ts +++ b/packages/webdriverio/tests/multiremote.test.ts @@ -5,7 +5,6 @@ import type { Capabilities } from '@wdio/types' import { multiremote } from '../src/index.js' vi.mock('fetch') -vi.mock('devtools') vi.mock('@wdio/logger', () => import(path.join(process.cwd(), '__mocks__', '@wdio/logger'))) const caps = (): Capabilities.MultiRemoteCapabilities => ({ diff --git a/packages/webdriverio/tests/overwriteCommand.test.ts b/packages/webdriverio/tests/overwriteCommand.test.ts index c17526b9b..535f8e964 100644 --- a/packages/webdriverio/tests/overwriteCommand.test.ts +++ b/packages/webdriverio/tests/overwriteCommand.test.ts @@ -3,7 +3,6 @@ import { describe, expect, test, vi } from 'vitest' import { remote, multiremote } from '../src/index.js' vi.mock('fetch') -vi.mock('devtools') vi.mock('@wdio/logger', () => import(path.join(process.cwd(), '__mocks__', '@wdio/logger'))) const remoteConfig = { @@ -146,8 +145,8 @@ describe('overwriteCommand', () => { expect(await browser.browserB.pause(10)).toBe(undefined) const results = await browser.pause(10) - expect(results[1]).toBeGreaterThanOrEqual(10) - expect(results[0]).toBe(undefined) + expect(results[0]).toBeGreaterThanOrEqual(10) + expect(results[1]).toBe(undefined) }) test('should be able to overwrite element command in multiremote mode', async () => { diff --git a/packages/webdriverio/tests/utils.test.ts b/packages/webdriverio/tests/utils.test.ts index ef6a32924..d5ba12bf4 100644 --- a/packages/webdriverio/tests/utils.test.ts +++ b/packages/webdriverio/tests/utils.test.ts @@ -153,14 +153,6 @@ describe('utils', () => { expect(result[0]).toEqual('\uE011') }) - it('should not convert unicode if devtools is used', () => { - const result = checkUnicode('Home', true) - - expect(Array.isArray(result)).toBe(true) - expect(result).toHaveLength(1) - expect(result[0]).toEqual('Home') - }) - it('should return an array without unicode', () => { const result = checkUnicode('foo') diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 58a25c159..e3c7271c1 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -311,68 +311,6 @@ importers: e2e/interop: {} - packages/devtools: - dependencies: - '@types/node': - specifier: ^20.1.0 - version: 20.11.20 - '@wdio/config': - specifier: 8.31.1 - version: link:../wdio-config - '@wdio/logger': - specifier: 8.28.0 - version: link:../wdio-logger - '@wdio/protocols': - specifier: 8.32.0 - version: link:../wdio-protocols - '@wdio/types': - specifier: 8.31.1 - version: link:../wdio-types - '@wdio/utils': - specifier: 8.31.1 - version: link:../wdio-utils - chrome-launcher: - specifier: ^1.0.0 - version: 1.1.0 - edge-paths: - specifier: ^3.0.5 - version: 3.0.5 - import-meta-resolve: - specifier: ^4.0.0 - version: 4.0.0 - puppeteer-core: - specifier: 22.3.0 - version: 22.3.0 - query-selector-shadow-dom: - specifier: ^1.0.0 - version: 1.0.1 - ua-parser-js: - specifier: ^1.0.37 - version: 1.0.37 - uuid: - specifier: ^9.0.0 - version: 9.0.1 - which: - specifier: ^4.0.0 - version: 4.0.0 - devDependencies: - '@types/ua-parser-js': - specifier: ^0.7.33 - version: 0.7.39 - '@types/uuid': - specifier: ^9.0.0 - version: 9.0.8 - '@types/which': - specifier: ^3.0.3 - version: 3.0.3 - '@wdio/globals': - specifier: 8.32.1 - version: link:../wdio-globals - - packages/devtools/build/cjs: {} - - packages/devtools/src/cjs: {} - packages/eslint-plugin-wdio: devDependencies: '@types/estree': @@ -1425,9 +1363,6 @@ importers: css-value: specifier: ^0.0.1 version: 0.0.1 - devtools: - specifier: ^8.14.0 - version: 8.32.3(typescript@5.3.3) grapheme-splitter: specifier: ^1.0.2 version: 1.0.4 @@ -1577,9 +1512,6 @@ importers: '@wdio/webdriver-mock-service': specifier: workspace:* version: link:../packages/wdio-webdriver-mock-service - devtools: - specifier: workspace:* - version: link:../packages/devtools webdriver: specifier: workspace:* version: link:../packages/webdriver @@ -6230,30 +6162,6 @@ packages: - supports-color dev: true - /@puppeteer/browsers@1.3.0(typescript@5.3.3): - resolution: {integrity: sha512-an3QdbNPkuU6qpxpbssxAbjRLJcF+eP4L8UqIY3+6n0sbaVxw5pz7PiCLy9g32XEZuoamUlV5ZQPnA6FxvkIHA==} - engines: {node: '>=16.0.0'} - hasBin: true - peerDependencies: - typescript: '>= 4.7.4' - peerDependenciesMeta: - typescript: - optional: true - dependencies: - debug: 4.3.4(supports-color@8.1.1) - extract-zip: 2.0.1 - http-proxy-agent: 5.0.0 - https-proxy-agent: 5.0.1 - progress: 2.0.3 - proxy-from-env: 1.1.0 - tar-fs: 2.1.1 - typescript: 5.3.3 - unbzip2-stream: 1.4.3 - yargs: 17.7.1 - transitivePeerDependencies: - - supports-color - dev: false - /@puppeteer/browsers@1.4.6(typescript@5.3.3): resolution: {integrity: sha512-x4BEjr2SjOPowNeiguzjozQbsc6h437ovD/wu+JpaenxVLm3jkgzHY2xOslMTp50HoTvQreMjiexiGQw1sqZlQ==} engines: {node: '>=16.3.0'} @@ -7820,6 +7728,7 @@ packages: /@tootallnate/once@2.0.0: resolution: {integrity: sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A==} engines: {node: '>= 10'} + dev: true /@tootallnate/quickjs-emscripten@0.23.0: resolution: {integrity: sha512-C5Mc6rdnsaJDjO3UpGW/CQTHtCKaYlScZTly4JIu97Jxo/odCiH0ITnDXSJPTOrEKk/ycSZ0AOgTmkDtkOsvIA==} @@ -8380,10 +8289,6 @@ packages: resolution: {integrity: sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==} dev: false - /@types/ua-parser-js@0.7.39: - resolution: {integrity: sha512-P/oDfpofrdtF5xw433SPALpdSchtJmY7nsJItf8h3KXqOslkbySh8zq4dSWXH2oTjRvJ5PczVEoCZPow6GicLg==} - dev: true - /@types/unist@2.0.10: resolution: {integrity: sha512-IfYcSBWE3hLpBg8+X2SEa8LVkJdJEkT2Ese2aaLs3ptGdVtABxndrMaxuFlQ1qdFf9Q5rDvDpxI3WwgvKFAsQA==} @@ -8410,10 +8315,6 @@ packages: /@types/which@2.0.2: resolution: {integrity: sha512-113D3mDkZDjo+EeUEHCFy0qniNc1ZpecGiAU7WSo7YDoSzolZIQKpYFHrPpjkB2nuyahcKfrmLXeQlh7gqJYdw==} - /@types/which@3.0.3: - resolution: {integrity: sha512-2C1+XoY0huExTbs8MQv1DuS5FS86+SEjdM9F/+GS61gg5Hqbtj8ZiDSx8MfWcyei907fIPbfPGCOrNUTnVHY1g==} - dev: true - /@types/ws@8.5.10: resolution: {integrity: sha512-vmQSUcfalpIq0R9q7uTo2lXs6eGIpt9wtnLdMv9LVpIjCA/+ufZRozlVoVelIYixx1ugCBKDhn89vnsEGOCx9A==} dependencies: @@ -10364,19 +10265,6 @@ packages: - supports-color dev: false - /chrome-launcher@1.1.0: - resolution: {integrity: sha512-rJYWeEAERwWIr3c3mEVXwNiODPEdMRlRxHc47B1qHPOolHZnkj7rMv1QSUfPoG6MgatWj5AxSpnKKR4QEwEQIQ==} - engines: {node: '>=12.13.0'} - hasBin: true - dependencies: - '@types/node': 20.11.20 - escape-string-regexp: 4.0.0 - is-wsl: 2.2.0 - lighthouse-logger: 2.0.1 - transitivePeerDependencies: - - supports-color - dev: false - /chrome-trace-event@1.0.3: resolution: {integrity: sha512-p3KULyQg4S7NIHixdwbGX+nFHkoBiA4YQmyWtjb8XngSKV124nJmRysgAeujbUVb15vh+RvFUfCPqU7rXk+hZg==} engines: {node: '>=6.0'} @@ -10389,15 +10277,6 @@ packages: devtools-protocol: 0.0.1147663 mitt: 3.0.0 - /chromium-bidi@0.4.9(devtools-protocol@0.0.1120988): - resolution: {integrity: sha512-u3DC6XwgLCA9QJ5ak1voPslCmacQdulZNCPsI3qNXxSnEcZS7DFIbww+5RM2bznMEje7cc0oydavRLRvOIZtHw==} - peerDependencies: - devtools-protocol: '*' - dependencies: - devtools-protocol: 0.0.1120988 - mitt: 3.0.0 - dev: false - /chromium-bidi@0.5.10(devtools-protocol@0.0.1249869): resolution: {integrity: sha512-4hsPE1VaLLM/sgNK/SlLbI24Ra7ZOuWAjA3rhw1lVCZ8ZiUgccS6cL5L/iqo4hjRcl5vwgYJ8xTtbXdulA9b6Q==} peerDependencies: @@ -11049,14 +10928,6 @@ packages: cross-spawn: 7.0.3 dev: true - /cross-fetch@3.1.6: - resolution: {integrity: sha512-riRvo06crlE8HiqOwIpQhxwdOk4fOeR7FVM/wXoxchFEqMNUjvbs3bfo4OTgMEMHzppd4DxFBDbyySj8Cv781g==} - dependencies: - node-fetch: 2.7.0 - transitivePeerDependencies: - - encoding - dev: false - /cross-fetch@3.1.8: resolution: {integrity: sha512-cvA+JwZoU0Xq+h6WkMvAUqPEYy92Obet6UdKLfW60qn99ftItKjB5T+BkyWOFWe2pUyfQ+IJHmpOTznqk1M6Kg==} dependencies: @@ -11720,10 +11591,6 @@ packages: dependencies: dequal: 2.0.3 - /devtools-protocol@0.0.1120988: - resolution: {integrity: sha512-39fCpE3Z78IaIPChJsP6Lhmkbf4dWXOmzLk/KFTdRkNk/0JymRIfUynDVRndV9HoDz8PyalK1UH21ST/ivwW5Q==} - dev: false - /devtools-protocol@0.0.1147663: resolution: {integrity: sha512-hyWmRrexdhbZ1tcJUGpO95ivbRhWXz++F4Ko+n21AY5PNln2ovoJw+8ZMNDTtip+CNFQfrtLVh/w4009dXO/eQ==} @@ -11743,32 +11610,6 @@ packages: resolution: {integrity: sha512-k0SCZMwj587w4F8QYbP5iIbSonL6sd3q8aVJch036r9Tv2t9b5/Oq7AiJ/FJvRuORm/pJNXZtrdNNWlpRnl56A==} dev: false - /devtools@8.32.3(typescript@5.3.3): - resolution: {integrity: sha512-Sgjb0jnzOBDQT+/K1JuFtfORQECSIQ31vldsoY6ojLqqlNJZYIjJMV0PiSGtd3FzQctQ6qgItDkzUqz35Kupjg==} - engines: {node: ^16.13 || >=18} - dependencies: - '@types/node': 20.11.20 - '@wdio/config': 8.32.3 - '@wdio/logger': 8.28.0 - '@wdio/protocols': 8.32.0 - '@wdio/types': 8.32.2 - '@wdio/utils': 8.32.3 - chrome-launcher: 1.1.0 - edge-paths: 3.0.5 - import-meta-resolve: 4.0.0 - puppeteer-core: 20.3.0(typescript@5.3.3) - query-selector-shadow-dom: 1.0.1 - ua-parser-js: 1.0.37 - uuid: 9.0.1 - which: 4.0.0 - transitivePeerDependencies: - - bufferutil - - encoding - - supports-color - - typescript - - utf-8-validate - dev: false - /didyoumean@1.2.2: resolution: {integrity: sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==} dev: true @@ -14580,6 +14421,7 @@ packages: debug: 4.3.4(supports-color@8.1.1) transitivePeerDependencies: - supports-color + dev: true /http-proxy-agent@7.0.2: resolution: {integrity: sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==} @@ -15981,15 +15823,6 @@ packages: - supports-color dev: false - /lighthouse-logger@2.0.1: - resolution: {integrity: sha512-ioBrW3s2i97noEmnXxmUq7cjIcVRjT5HBpAYy8zE11CxU9HqlWHHeRxfeN1tn8F7OEMVPIC9x1f8t3Z7US9ehQ==} - dependencies: - debug: 2.6.9 - marky: 1.2.5 - transitivePeerDependencies: - - supports-color - dev: false - /lighthouse-stack-packs@1.12.1: resolution: {integrity: sha512-i4jTmg7tvZQFwNFiwB+nCK6a7ICR68Xcwo+VIVd6Spi71vBNFUlds5HiDrSbClZdkQDON2Bhqv+KKJIo5zkPeA==} dev: false @@ -19424,29 +19257,6 @@ packages: dependencies: escape-goat: 4.0.0 - /puppeteer-core@20.3.0(typescript@5.3.3): - resolution: {integrity: sha512-264pBrIui5bO6NJeOcbJrLa0OCwmA4+WK00JMrLIKTfRiqe2gx8KWTzLsjyw/bizErp3TKS7vt/I0i5fTC+mAw==} - engines: {node: '>=16.0.0'} - peerDependencies: - typescript: '>= 4.7.4' - peerDependenciesMeta: - typescript: - optional: true - dependencies: - '@puppeteer/browsers': 1.3.0(typescript@5.3.3) - chromium-bidi: 0.4.9(devtools-protocol@0.0.1120988) - cross-fetch: 3.1.6 - debug: 4.3.4(supports-color@8.1.1) - devtools-protocol: 0.0.1120988 - typescript: 5.3.3 - ws: 8.13.0 - transitivePeerDependencies: - - bufferutil - - encoding - - supports-color - - utf-8-validate - dev: false - /puppeteer-core@20.9.0(typescript@5.3.3): resolution: {integrity: sha512-H9fYZQzMTRrkboEfPmf7m3CLDN6JvbxXA3qTtS+dFt27tR+CsFHzPsT6pzp6lYL6bJbAPaR0HaPO6uSi+F94Pg==} engines: {node: '>=16.3.0'} @@ -22282,10 +22092,6 @@ packages: engines: {node: '>=14.17'} hasBin: true - /ua-parser-js@1.0.37: - resolution: {integrity: sha512-bhTyI94tZofjo+Dn8SN6Zv8nBDvyXTymAdM3LDI/0IboIUwTu1rEhW7v2TfiVsoYWgkQ4kOVqnI8APUFbIQIFQ==} - dev: false - /uc.micro@1.0.6: resolution: {integrity: sha512-8Y75pvTYkLJW2hWQHXxoqRgV7qb9B+9vFEtidML+7koHUFapnVJAZ6cKs+Qjz5Aw3aZWHMC6u0wJE3At+nSGwA==} dev: true diff --git a/scripts/build.ts b/scripts/build.ts index b79fb8a77..63bbc65e4 100755 --- a/scripts/build.ts +++ b/scripts/build.ts @@ -27,7 +27,6 @@ const PKG_TO_COMPILE_FIRST = [ 'wdio-utils', 'wdio-config', 'wdio-repl', - 'devtools', 'webdriver', 'webdriverio' ] @@ -54,7 +53,7 @@ const ESM_CJS_PACKAGES = [ const CJS_PACKAGES = ['wdio-smoke-test-cjs-service'] -const ESM_PACKAGES_WITH_CJS_FOLDER = ['devtools', 'wdio-cli', 'webdriver', 'webdriverio'] +const ESM_PACKAGES_WITH_CJS_FOLDER = ['wdio-cli', 'webdriver', 'webdriverio'] const packages = getSubPackages() /** diff --git a/tests/package.json b/tests/package.json index c2d532f86..960cc70db 100644 --- a/tests/package.json +++ b/tests/package.json @@ -4,7 +4,6 @@ "type": "module", "dependencies": { "webdriver": "workspace:*", - "devtools": "workspace:*", "webdriverio": "workspace:*", "@wdio/cli": "workspace:*", "@wdio/config": "workspace:*", diff --git a/tests/typings/devtools/async.ts b/tests/typings/devtools/async.ts deleted file mode 100644 index 4a0489b36..000000000 --- a/tests/typings/devtools/async.ts +++ /dev/null @@ -1,17 +0,0 @@ -import DevTools from 'devtools' -import { expectType } from 'tsd' - -async function bar () { - const client = await DevTools.newSession({ - capabilities: { - browserName: 'chrome' - } - }) - await client.setTimeouts(1, 2, 3) - expectType(await client.getTitle()) - - client.createWindow('tab') - client.createWindow('window') -} - -export default {} diff --git a/tests/typings/devtools/tsconfig.json b/tests/typings/devtools/tsconfig.json deleted file mode 100644 index 0e2272043..000000000 --- a/tests/typings/devtools/tsconfig.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "compilerOptions": { - "outDir": "dist", - "noImplicitAny": true, - "lib": ["es2018", "DOM"], - "types": [ - "devtools" - ] - } -} diff --git a/tests/typings/setup.js b/tests/typings/setup.js index 64b6b1884..33c8fd46e 100644 --- a/tests/typings/setup.js +++ b/tests/typings/setup.js @@ -12,11 +12,10 @@ const tsDir = tsVersion ? path.join(__dirname, '@typescript', `ts${tsVersion}`) // TypeScript project root for testing particular typings const outDirs = [ - 'devtools', 'webdriverio', 'webdriver', 'mocha', 'jasmine', 'cucumber' + 'webdriverio', 'webdriver', 'mocha', 'jasmine', 'cucumber' ] const packages = { - 'devtools': 'packages/devtools', 'webdriver': 'packages/webdriver', 'webdriverio': 'packages/webdriverio', '@wdio/globals': 'packages/wdio-globals', diff --git a/tests/typings/webdriverio/async.ts b/tests/typings/webdriverio/async.ts index 9f4bc96f2..4d53af5b7 100644 --- a/tests/typings/webdriverio/async.ts +++ b/tests/typings/webdriverio/async.ts @@ -347,7 +347,6 @@ async function bar() { expectType(browser.isMobile) expectType(browser.isAndroid) expectType(browser.isIOS) - expectType(browser.isDevTools) expectType(browser.isMobile) // shadow$ shadow$$ diff --git a/tests/typings/webdriverio/tsconfig.json b/tests/typings/webdriverio/tsconfig.json index a56b78e5b..a433633d8 100644 --- a/tests/typings/webdriverio/tsconfig.json +++ b/tests/typings/webdriverio/tsconfig.json @@ -4,7 +4,6 @@ "noImplicitAny": true, "lib": ["es2018", "dom"], "types": [ - "devtools", "@wdio/utils", "@wdio/globals/types", "@wdio/allure-reporter", diff --git a/vitest.config.ts b/vitest.config.ts index fefd2e1e6..48a045129 100644 --- a/vitest.config.ts +++ b/vitest.config.ts @@ -23,10 +23,6 @@ export default defineConfig({ '**/build/**', '**/cjs/*.ts', '**/*.test.ts', - // we are using e2e tests for ensuring the functionality works - // check out the ./e2e folder - 'packages/devtools/src/commands', - 'packages/devtools/src/scripts', 'packages/webdriver/src/bidi/handler.ts' ], watermarks: { diff --git a/website/blog/2019-09-16-devtools.md b/website/blog/2019-09-16-devtools.md index e9bad39b6..9e177acf7 100644 --- a/website/blog/2019-09-16-devtools.md +++ b/website/blog/2019-09-16-devtools.md @@ -5,6 +5,12 @@ authorURL: http://github.com/christian-bromann authorImageURL: https://s.gravatar.com/avatar/d98b16d7c93d15865f34a225dd4b1254?s=80 --- +:::warning Feature Deprecation + +The announced feature in this blog post has been deprecated starting with WebdriverIO v9. + +::: + Over the last few years a lot of new testing frameworks have evolved that provide different capabilities but also come with certain limitations. While tools like [Selenium](https://www.seleniumhq.org/) are often seen as slow and flaky and others like [Cypress.io](https://www.cypress.io/) or [Puppeteer](https://pptr.dev/) are hyped as the future in the testing space. In reality all tools have their own use cases, have different levels of support and are based on a completely different architecture. Comparing them is rather useless as it ends up comparing apples with oranges. In larger test suites flakiness and race conditions can happen using all of these tools so that we can safely say that up to this point none of them (including WebdriverIO) has found the ultimate solution for world `/(domin|autom)ation/`. At the end of the day there are more or less two different approaches to how you can automate a browser. One is using an official W3C web standard called [WebDriver](https://w3c.github.io/webdriver/) and the other is the native browser interface that some of the browsers expose these days. The WebDriver protocol is the de-facto standard automation technique that allows you to not only automate all desktop browsers but also run automation on arbitrary user agents that include mobile devices, desktop applications or even Smart TVs. If you don't use WebDriver your automation framework most likely uses the native browser interfaces to run its automation on. While in the past every browser had its own (often not documented) protocol, these days a lot of browsers, including Chrome, Edge and soon even Firefox, come with a somewhat unified interface that is close to what is called the [Chrome DevTools Protocol](https://chromedevtools.github.io/devtools-protocol/). While WebDriver provides true cross-browser support and allows you to run tests on a large scale in the cloud using vendors like [Sauce Labs](https://saucelabs.com/), native browser interfaces often allow many more automation capabilities like listening and interacting with network or DOM events while often being limited to a single browser only. diff --git a/website/docs/AutomationProtocols.md b/website/docs/AutomationProtocols.md index dd4da4998..3c5c0c090 100644 --- a/website/docs/AutomationProtocols.md +++ b/website/docs/AutomationProtocols.md @@ -3,17 +3,19 @@ id: automationProtocols title: Automation Protocols --- -With WebdriverIO, you can choose between multiple automation technologies when running your E2E tests locally or in the cloud. By default WebdriverIO will always check for a browser driver that is compliant to the WebDriver protocol on `localhost:4444`. If it can't find such driver it falls back to use Chrome DevTools using Puppeteer under the hood. +With WebdriverIO, you can choose between multiple automation technologies when running your E2E tests locally or in the cloud. By default, WebdriverIO will attempt to start a local automation session using the [WebDriver Bidi](https://w3c.github.io/webdriver-bidi/) protocol. -Nearly all modern browsers that support [WebDriver](https://w3c.github.io/webdriver/) also support another native interface called [DevTools](https://chromedevtools.github.io/devtools-protocol/) that can be used for automation purposes. +## WebDriver Bidi Protocol -Both have advantages and disadvantages, depending on your use case and environment. +The [WebDriver Bidi](https://w3c.github.io/webdriver-bidi/) is an automation protocol to automate browsers using bi-directional communication. It's the successor of the [WebDriver](https://w3c.github.io/webdriver/) protocol and enables a lot more introspection capabilities for various testing use cases. + +This protocol is currently under development and new primitives might be added in the future. All browser vendors have committed to implementing this web standard and a lot of [primitives](https://wpt.fyi/results/webdriver/tests/bidi?label=experimental&label=master&aligned) have already been landed in browsers. ## WebDriver Protocol > [WebDriver](https://w3c.github.io/webdriver/) is a remote control interface that enables introspection and control of user agents. It provides a platform- and language-neutral wire protocol as a way for out-of-process programs to remotely instruct the behavior of web browsers. -The WebDriver protocol was designed to automate a browser from the user perspective, meaning that everything a user is able to do, you can do with the browser. It provides a set of commands that abstract away common interactions with an application (e.g., navigating, clicking, or reading the state of an element). Since it is a web standard, it is well supported across all major browser vendors, and also is being used as underlying protocol for mobile automation using [Appium](http://appium.io). +The WebDriver protocol was designed to automate a browser from the user perspective, meaning that everything a user is able to do, you can do with the browser. It provides a set of commands that abstract away common interactions with an application (e.g., navigating, clicking, or reading the state of an element). Since it is a web standard, it is well supported across all major browser vendors and also is being used as an underlying protocol for mobile automation using [Appium](http://appium.io). To use this automation protocol, you need a proxy server that translates all commands and executes them in the target environment (i.e. the browser or the mobile app). @@ -30,180 +32,3 @@ For any kind of mobile automation, you’ll need to install and setup [Appium](h There are also plenty of services that allow you to run your automation test in the cloud at high scale. Instead of having to setup all these drivers locally, you can just talk to these services (e.g. [Sauce Labs](https://saucelabs.com)) in the cloud and inspect the results on their platform. The communication between test script and automation environment will look as follows: ![WebDriver Setup](/img/webdriver.png) - -### Advantages - -- Official W3C web standard, supported by all major browsers -- Simplified protocol that covers common user interactions -- Support for mobile automation (and even native desktop apps) -- Can be used locally as well as in the cloud through services like [Sauce Labs](https://saucelabs.com) - -### Disadvantages - -- Not designed for in-depth browser analysis (e.g., tracing or intercepting network events) -- Limited set of automation capabilities (e.g., no support to throttle CPU or network) -- Additional effort to set up browser driver with selenium-standalone/chromedriver/etc - -## DevTools Protocol - -:::warning Deprecation - -WebdriverIO is deprecating the use of Chrome Devtools as automation protocol through a WebDriver like interface. Instead, you can use the [`getPuppeteer`](/docs/api/browser/getPuppeteer) command to get ahold of a Puppeteer instance for Chrome Devtools introspection and automation. - -::: - -The DevTools interface is a native browser interface that is usually being used to debug the browser from a remote application (e.g., [Chrome DevTools](https://developers.google.com/web/tools/chrome-devtools/)). Next to its capabilities to inspect the browser in nearly all possible forms, it can also be used to control it. - -While every browser used to have its own internal DevTools interface that was not really exposed to the user, more and more browsers are now adopting the [Chrome DevTools Protocol](https://chromedevtools.github.io/devtools-protocol/). It is used to either debug a web application using Chrome DevTools or control Chrome using tools like [Puppeteer](https://pptr.dev). - -The communication happens without any proxy, directly to the browser using WebSockets: - -![DevTools Setup](/img/devtools.png) - -WebdriverIO allows you to use the DevTools capabilities as an alternative automation technology for WebDriver if you have special requirements to automate the browser. With the [`devtools`](https://www.npmjs.com/package/devtools) NPM package, you can use the same commands that WebDriver provides, which then can be used by WebdriverIO and the WDIO testrunner to run its useful commands on top of that protocol. It uses Puppeteer to under the hood and allows you to run a sequence of commands with Puppeteer if needed. - -To use DevTools as your automation protocol switch the `automationProtocol` flag to `devtools` in your configurations or just run WebdriverIO without a browser driver run in the background. - - - - -```js title="wdio.conf.js" -export const config = { - // ... - automationProtocol: 'devtools' - // ... -} -``` -```js title="devtools.e2e.js" -describe('my test', () => { - it('can use Puppeteer as automation fallback', async () => { - // WebDriver command - await browser.url('https://webdriver.io') - - // get instance (https://pptr.dev/#?product=Puppeteer&version=v5.2.1&show=api-class-browser) - const puppeteer = await browser.getPuppeteer() - - // use Puppeteer interfaces - const page = (await puppeteer.pages())[0] - await page.setRequestInterception(true) - page.on('request', interceptedRequest => { - if (interceptedRequest.url().endsWith('webdriverio.png')) { - return interceptedRequest.continue({ - url: 'https://webdriver.io/img/puppeteer.png' - }) - } - - interceptedRequest.continue() - }) - - // continue with WebDriver commands - await browser.url('https://webdriver.io') - - /** - * WebdriverIO logo is no replaced with the Puppeteer logo - */ - }) -}) -``` - -__Note:__ there is no need to have either `selenium-standalone` or `chromedriver` services installed. - -We recommend wrapping your Puppeteer calls within the `call` command, so that all calls are executed before WebdriverIO continues with the next WebDriver command. - - - - -```js -import { remote } from 'webdriverio' - -const browser = await remote({ - automationProtocol: 'devtools', - capabilities: { - browserName: 'chrome' - } -}) - -// WebDriver command -await browser.url('https://webdriver.io') - -// get instance (https://pptr.dev/#?product=Puppeteer&version=v5.2.1&show=api-class-browser) -const puppeteer = await browser.getPuppeteer() - -// switch to Puppeteer to intercept requests -const page = (await puppeteer.pages())[0] -await page.setRequestInterception(true) -page.on('request', interceptedRequest => { - if (interceptedRequest.url().endsWith('webdriverio.png')) { - return interceptedRequest.continue({ - url: 'https://user-images.githubusercontent.com/10379601/29446482-04f7036a-841f-11e7-9872-91d1fc2ea683.png' - }) - } - - interceptedRequest.continue() -}) - -// continue with WebDriver commands -await browser.refresh() -await browser.pause(2000) - -await browser.deleteSession() -``` - - - - -By accessing the Puppeteer interface, you have access to a variety of new capabilities to automate or inspect the browser and your application, e.g. intercepting network requests (see above), tracing the browser, throttle CPU or network capabilities, and much more. - -### `wdio:devtoolsOptions` Capability - -If you run WebdriverIO tests through the DevTools package, you can apply [custom Puppeteer options](https://pptr.dev/#?product=Puppeteer&version=v8.0.0&show=api-puppeteerlaunchoptions). These options will be directly passed into the [`launch`](https://pptr.dev/#?product=Puppeteer&version=v8.0.0&show=api-puppeteerlaunchoptions) or [`connect`](https://pptr.dev/#?product=Puppeteer&version=v8.0.0&show=api-puppeteerconnectoptions) methods of Puppeteer. Other custom devtools options are the following: - -#### customPort -Start Chrome on a custom port. - -Type: `number`
-Default: `9222` (default of Puppeteer) - -Note: if you pass in `goog:chromeOptions/debuggerAddress`, `wdio:devtoolsOptions/browserWSEndpoint` or `wdio:devtoolsOptions/browserURL` options, WebdriverIO will try to connect with given connection details rather than starting a browser. For example you can connect to Testingbots cloud via: - -```js -import { format } from 'util' -import { remote } from 'webdriverio' - -(async () => { - const browser = await remote({ - capabilities: { - 'wdio:devtoolsOptions': { - browserWSEndpoint: format( - `wss://cloud.testingbot.com?key=%s&secret=%s&browserName=chrome&browserVersion=latest`, - process.env.TESTINGBOT_KEY, - process.env.TESTINGBOT_SECRET - ) - } - } - }) - - await browser.url('https://webdriver.io') - - const title = await browser.getTitle() - console.log(title) // returns "should return "WebdriverIO - click"" - - await browser.deleteSession() -})() -``` - -### Advantages - -- Access to more automation capabilities (e.g. network interception, tracing etc.) -- No need to manage browser drivers - -### Disadvantages - -- Only supports Chromium based browser (e.g. Chrome, Chromium Edge) and (partially) Firefox -- Does __not__ support execution on cloud vendors such as Sauce Labs, BrowserStack etc.