TypeScript set up overhaul (#6302)

This commit is contained in:
Christian Bromann 2021-01-20 16:43:45 +01:00 committed by christian-bromann
parent a25617cc22
commit 1a7d2626c5
No known key found for this signature in database
GPG key ID: 355B5D0EA83B4D19
504 changed files with 7090 additions and 13181 deletions

3
.gitignore vendored
View file

@ -37,8 +37,9 @@ node_modules/
jspm_packages/
# Typescript v1 declaration files
typings/
/packages/wdio-protocols/src/commands
!tests/typings
!typings/types.ts
tests/typings/**/dist
# Optional npm cache directory

View file

@ -1,4 +1,4 @@
namespace NodeJS {
declare namespace NodeJS {
interface Process {
_debugProcess: (pid: number) => void
_debugEnd: (pid: number) => void

View file

@ -5,11 +5,11 @@ Feature: Example feature
Scenario: Get size of an element
Given I go on the website "https://github.com/"
Then should the element ".header-logged-out a" be 32px wide and 35px high
Then should the element ".header-logged-out a" be 32px wide and 34px high
Scenario: Get title of website
Given I go on the website "https://github.com/"
Then should the title of the page be "The world’s leading software development platform · GitHub"
Then should the title of the page be "GitHub: Where the world builds software · GitHub"
Scenario: Data Tables
Given I go on the website "http://todomvc.com/examples/react/#/"

View file

@ -9,7 +9,7 @@
* $ cucumber.js
*/
const { Given, When, Then } = require('cucumber')
const { Given, When, Then } = require('@cucumber/cucumber')
Given(/^I go on the website "([^"]*)"$/, (url) => {
browser.url(url)

View file

@ -25,7 +25,7 @@ module.exports = {
collectCoverage: true,
coverageThreshold: {
global: {
branches: 93,
branches: 92,
functions: 97,
lines: 98,
statements: 98

View file

@ -33,30 +33,20 @@
"test": "run-s test:depcheck test:eslint test:typings test:coverage test:smoke",
"test:depcheck": "node ./scripts/depcheck.js",
"test:eslint": "eslint --cache packages examples scripts tests",
"test:typings": "npm run generate:typings && node tests/typings/setup && npm run ts && npm run clean:tests && node tests/typings/setup --ts=3.8 && npm run ts && npm run clean:tests",
"test:typings": "node tests/typings/setup && npm run ts && npm run clean:tests && node tests/typings/setup --ts=3.9 && npm run ts && npm run clean:tests",
"test:coverage": "node --max-old-space-size=8192 ./node_modules/jest/bin/jest.js --coverage --logHeapUsage",
"test:smoke": "ts-node ./tests/smoke.runner.js",
"test:e2e": "jest --config e2e/jest.config.js",
"test:e2e:edge": "npm run test:e2e -- --testMatch '**/edge.e2e.js'",
"test:e2e:firefox": "npm run test:e2e -- --testMatch '<rootDir>/standalone/firefox.e2e.js'",
"ts": "run-p ts:*",
"ts:sync": "cd tests/typings/sync && npx tsc",
"ts:webdriver": "cd tests/typings/webdriver && npx tsc",
"ts:devtools": "cd tests/typings/devtools && npx tsc",
"ts:sync-applitools": "cd tests/typings/sync-applitools && npx tsc",
"ts:sync-browserstack": "cd tests/typings/sync-browserstack && npx tsc",
"ts:sync-devtools-service": "cd tests/typings/sync-devtools-service && npx tsc",
"ts:sync-saucelabs": "cd tests/typings/sync-saucelabs && npx tsc",
"ts:sync-mocha": "cd tests/typings/sync-mocha && npx tsc",
"ts:sync-jasmine": "cd tests/typings/sync-jasmine && npx tsc",
"ts:sync-cucumber": "cd tests/typings/sync-cucumber && npx tsc",
"ts:webdriverio": "cd tests/typings/webdriverio && npx tsc",
"ts:webdriverio-applitools": "cd tests/typings/webdriverio-applitools && npx tsc",
"ts:webdriverio-browserstack": "cd tests/typings/webdriverio-browserstack && npx tsc",
"ts:webdriverio-saucelabs": "cd tests/typings/webdriverio-saucelabs && npx tsc",
"ts:webdriverio-devtools": "cd tests/typings/sync-devtools && npx tsc",
"ts:webdriverio-devtools-service": "cd tests/typings/webdriverio-devtools-service && npx tsc",
"ts:webdriverio-mocha": "cd tests/typings/webdriverio-mocha && npx tsc",
"ts:webdriverio-jasmine": "cd tests/typings/webdriverio-jasmine && npx tsc",
"ts:mocha": "cd tests/typings/mocha && npx tsc",
"ts:jasmine": "cd tests/typings/jasmine && npx tsc",
"ts:cucumber": "cd tests/typings/cucumber && npx tsc",
"ts:sync": "cd tests/typings/sync && npx tsc",
"watch": "node ./scripts/build --watch",
"version": "npm run changelog && git add CHANGELOG.md"
},
@ -82,6 +72,7 @@
"@typescript-eslint/eslint-plugin": "^4.4.0",
"@typescript-eslint/parser": "^4.4.0",
"aws-sdk": "^2.539.0",
"camelcase": "^6.2.0",
"cheerio": "^1.0.0-rc.3",
"codecov": "^3.6.1",
"copyfiles": "^2.1.1",

View file

@ -9,7 +9,7 @@
"engines": {
"node": ">=12.0.0"
},
"types": "./devtools.d.ts",
"types": "./build/index.d.ts",
"typeScriptVersion": "3.8.3",
"repository": {
"type": "git",
@ -25,6 +25,7 @@
"@wdio/config": "6.12.1",
"@wdio/logger": "6.10.10",
"@wdio/protocols": "6.12.0",
"@wdio/types": "^6.10.6",
"@wdio/utils": "6.11.0",
"chrome-launcher": "^0.13.1",
"edge-paths": "^2.1.0",

View file

@ -0,0 +1,66 @@
import acceptAlert from './commands/acceptAlert'
import addCookie from './commands/addCookie'
import back from './commands/back'
import closeWindow from './commands/closeWindow'
import createWindow from './commands/createWindow'
import deleteAllCookies from './commands/deleteAllCookies'
import deleteCookie from './commands/deleteCookie'
import deleteSession from './commands/deleteSession'
import dismissAlert from './commands/dismissAlert'
import elementClear from './commands/elementClear'
import elementClick from './commands/elementClick'
import elementSendKeys from './commands/elementSendKeys'
import executeAsyncScript from './commands/executeAsyncScript'
import executeScript from './commands/executeScript'
import findElement from './commands/findElement'
import findElementFromElement from './commands/findElementFromElement'
import findElements from './commands/findElements'
import findElementsFromElement from './commands/findElementsFromElement'
import forward from './commands/forward'
import getActiveElement from './commands/getActiveElement'
import getAlertText from './commands/getAlertText'
import getAllCookies from './commands/getAllCookies'
import getElementAttribute from './commands/getElementAttribute'
import getElementCSSValue from './commands/getElementCSSValue'
import getElementComputedLabel from './commands/getElementComputedLabel'
import getElementComputedRole from './commands/getElementComputedRole'
import getElementProperty from './commands/getElementProperty'
import getElementRect from './commands/getElementRect'
import getElementTagName from './commands/getElementTagName'
import getElementText from './commands/getElementText'
import getNamedCookie from './commands/getNamedCookie'
import getPageSource from './commands/getPageSource'
import getTimeouts from './commands/getTimeouts'
import getTitle from './commands/getTitle'
import getUrl from './commands/getUrl'
import getWindowHandle from './commands/getWindowHandle'
import getWindowHandles from './commands/getWindowHandles'
import getWindowRect from './commands/getWindowRect'
import isElementEnabled from './commands/isElementEnabled'
import isElementSelected from './commands/isElementSelected'
import navigateTo from './commands/navigateTo'
import newSession from './commands/newSession'
import performActions from './commands/performActions'
import refresh from './commands/refresh'
import releaseActions from './commands/releaseActions'
import sendAlertText from './commands/sendAlertText'
import setTimeouts from './commands/setTimeouts'
import setWindowRect from './commands/setWindowRect'
import status from './commands/status'
import switchToFrame from './commands/switchToFrame'
import switchToParentFrame from './commands/switchToParentFrame'
import switchToWindow from './commands/switchToWindow'
import takeElementScreenshot from './commands/takeElementScreenshot'
import takeScreenshot from './commands/takeScreenshot'
export {
acceptAlert, addCookie, back, closeWindow, createWindow, deleteAllCookies, deleteCookie, deleteSession,
dismissAlert, elementClear, elementClick, elementSendKeys, executeAsyncScript, executeScript, findElement,
findElementFromElement, findElements, findElementsFromElement, forward, getActiveElement, getAlertText,
getAllCookies, getElementAttribute, getElementCSSValue, getElementComputedLabel, getElementComputedRole,
getElementProperty, getElementRect, getElementTagName, getElementText, getNamedCookie, getPageSource,
getTimeouts, getTitle, getUrl, getWindowHandle, getWindowHandles, getWindowRect, isElementEnabled,
isElementSelected, navigateTo, newSession, performActions, refresh, releaseActions, sendAlertText,
setTimeouts, setWindowRect, status, switchToFrame, switchToParentFrame, switchToWindow, takeElementScreenshot,
takeScreenshot
}

View file

@ -1,11 +1,11 @@
import type DevToolsDriver from '../devtoolsdriver'
/**
* The Accept Alert command accepts a simple dialog if present, otherwise error.
*
* @alias browser.acceptAlert
* @see https://w3c.github.io/webdriver/#dfn-accept-alert
*/
import type DevToolsDriver from '../devtoolsdriver'
export default async function acceptAlert(this: DevToolsDriver) {
if (!this.activeDialog) {
throw new Error('no such alert')

View file

@ -1,3 +1,7 @@
import { Cookie } from '@wdio/protocols'
import type DevToolsDriver from '../devtoolsdriver'
/**
* The Add Cookie command adds a single cookie to the cookie store
* associated with the active document's address.
@ -6,11 +10,9 @@
* @see https://w3c.github.io/webdriver/#dfn-adding-a-cookie
* @param {object} cookie A JSON object representing a cookie. It must have at least the name and value fields and could have more, including expiry-time and so on
*/
import type DevToolsDriver from '../devtoolsdriver'
export default async function addCookie(
this: DevToolsDriver,
{ cookie }: { cookie: WebDriver.Cookie }
{ cookie }: { cookie: Cookie }
) {
const page = this.getPageHandle()

View file

@ -1,3 +1,5 @@
import type DevToolsDriver from '../devtoolsdriver'
/**
* The Back command causes the browser to traverse one step backward
* in the joint session history of the current top-level browsing context.
@ -6,8 +8,6 @@
* @alias browser.back
* @see https://w3c.github.io/webdriver/#dfn-back
*/
import type DevToolsDriver from '../devtoolsdriver'
export default async function back (this: DevToolsDriver) {
delete this.currentFrame
const page = this.getPageHandle()

View file

@ -1,3 +1,6 @@
import { v4 as uuidv4 } from 'uuid'
import type DevToolsDriver from '../devtoolsdriver'
/**
* The Close Window command closes the current top-level browsing context.
* Once done, if there are no more top-level browsing contexts open,
@ -6,10 +9,6 @@
* @alias browser.closeWindow
* @see https://w3c.github.io/webdriver/#dfn-close-window
*/
import { v4 as uuidv4 } from 'uuid'
import type DevToolsDriver from '../devtoolsdriver'
export default async function closeWindow (this: DevToolsDriver) {
delete this.currentFrame

View file

@ -1,12 +1,3 @@
/**
* Create a new top-level browsing context.
*
* @alias browser.createWindow
* @see https://w3c.github.io/webdriver/#new-window
* @param {string} type Set to 'tab' if the newly created window shares an OS-level window with the current browsing context, or 'window' otherwise.
* @return {object} New window object containing 'handle' with the value of the handle and 'type' with the value of the created window type
*/
import { v4 as uuidv4 } from 'uuid'
import type DevToolsDriver from '../devtoolsdriver'
@ -16,6 +7,14 @@ const WINDOW_FEATURES = 'menubar=1,toolbar=1,location=1,resizable=1,scrollbars=1
const NEW_PAGE_URL = 'about:blank'
const DEFAULT_WINDOW_TYPE = 'tab'
/**
* Create a new top-level browsing context.
*
* @alias browser.createWindow
* @see https://w3c.github.io/webdriver/#new-window
* @param {string} type Set to 'tab' if the newly created window shares an OS-level window with the current browsing context, or 'window' otherwise.
* @return {object} New window object containing 'handle' with the value of the handle and 'type' with the value of the created window type
*/
export default async function createWindow (
this: DevToolsDriver,
{ type }: { type: 'window' | 'tab' }

View file

@ -1,3 +1,5 @@
import type DevToolsDriver from '../devtoolsdriver'
/**
* The Delete All Cookies command allows deletion of all cookies
* associated with the active document's address.
@ -5,8 +7,6 @@
* @alias browser.deleteAllCookies
* @see https://w3c.github.io/webdriver/#dfn-delete-all-cookies
*/
import type DevToolsDriver from '../devtoolsdriver'
export default async function deleteAllCookies (this: DevToolsDriver) {
const page = this.getPageHandle()
const cookies = await page.cookies()

View file

@ -1,3 +1,5 @@
import type DevToolsDriver from '../devtoolsdriver'
/**
* The Delete Cookie command allows you to delete either a single cookie by parameter name,
* or all the cookies associated with the active document's address if name is undefined.
@ -6,8 +8,6 @@
* @see https://w3c.github.io/webdriver/#dfn-delete-cookie
* @param {string} name name of the cookie to delete
*/
import type DevToolsDriver from '../devtoolsdriver'
export default async function deleteCookie (
this: DevToolsDriver,
{ name }: { name: string }

View file

@ -1,3 +1,5 @@
import type DevToolsDriver from '../devtoolsdriver'
/**
* The Delete Session command closes any top-level browsing contexts associated
* with the current session, terminates the connection, and finally closes the current session.
@ -5,8 +7,6 @@
* @alias browser.deleteSession
* @see https://w3c.github.io/webdriver/#dfn-delete-session
*/
import type DevToolsDriver from '../devtoolsdriver'
export default async function deleteSession (this: DevToolsDriver) {
await this.browser.close()
this.windows.clear()

View file

@ -1,3 +1,5 @@
import type DevToolsDriver from '../devtoolsdriver'
/**
* The Dismiss Alert command dismisses a simple dialog if present, otherwise error.
* A request to dismiss an alert user prompt, which may not necessarily have a dismiss button,
@ -6,8 +8,6 @@
* @alias browser.dismissAlert
* @see https://w3c.github.io/webdriver/#dfn-dismiss-alert
*/
import type DevToolsDriver from '../devtoolsdriver'
export default async function dismissAlert (this: DevToolsDriver) {
if (!this.activeDialog) {
throw new Error('no such alert')

View file

@ -1,3 +1,7 @@
import command from '../scripts/elementClear'
import { getStaleElementError } from '../utils'
import type DevToolsDriver from '../devtoolsdriver'
/**
* The Element Clear command scrolls into view an editable or resettable element and then attempts
* to clear its selected files or text content.
@ -6,11 +10,6 @@
* @see https://w3c.github.io/webdriver/#dfn-element-clear
* @param {string} elementId the id of an element returned in a previous call to Find Element(s)
*/
import command from '../scripts/elementClear'
import { getStaleElementError } from '../utils'
import type DevToolsDriver from '../devtoolsdriver'
export default async function elementClear (
this: DevToolsDriver,
{ elementId }: { elementId: string }

View file

@ -1,3 +1,8 @@
import getElementTagName from './getElementTagName'
import selectOptionScript from '../scripts/selectOption'
import { getStaleElementError } from '../utils'
import type DevToolsDriver from '../devtoolsdriver'
/**
* The Element Click command scrolls into view the element if it is not already pointer-interactable,
* and clicks its in-view center point. If the element's center point is obscured by another element,
@ -9,12 +14,6 @@
* @see https://w3c.github.io/webdriver/#dfn-element-click
* @param {string} elementId the id of an element returned in a previous call to Find Element(s)
*/
import getElementTagName from './getElementTagName'
import selectOptionScript from '../scripts/selectOption'
import { getStaleElementError } from '../utils'
import type DevToolsDriver from '../devtoolsdriver'
export default async function elementClick (
this: DevToolsDriver,
{ elementId }: { elementId: string }

View file

@ -1,3 +1,8 @@
import path from 'path'
import { getStaleElementError } from '../utils'
import type DevToolsDriver from '../devtoolsdriver'
/**
* The Element Send Keys command scrolls into view the form control element and then sends
* the provided keys to the element. In case the element is not keyboard-interactable,
@ -9,12 +14,6 @@
* @param {string} elementId the id of an element returned in a previous call to Find Element(s)
* @param {string} text string to send as keystrokes to the element
*/
import path from 'path'
import { getStaleElementError } from '../utils'
import type DevToolsDriver from '../devtoolsdriver'
export default async function elementSendKeys (
this: DevToolsDriver,
{ elementId, text }: { elementId: string, text: string }

View file

@ -1,3 +1,8 @@
import command from '../scripts/executeAsyncScript'
import { transformExecuteArgs, transformExecuteResult } from '../utils'
import { SERIALIZE_PROPERTY, SERIALIZE_FLAG } from '../constants'
import type DevToolsDriver from '../devtoolsdriver'
/**
* The Execute Async Script command causes JavaScript to execute as an anonymous function.
* Unlike the Execute Script command, the result of the function is ignored.
@ -10,12 +15,6 @@
* @param {*[]} args an array of JSON values which will be deserialized and passed as arguments to your function
* @return * Either the return value of your script, the fulfillment of the Promise returned by your script, or the error which was the reason for your script's returned Promise's rejection.
*/
import command from '../scripts/executeAsyncScript'
import { transformExecuteArgs, transformExecuteResult } from '../utils'
import { SERIALIZE_PROPERTY, SERIALIZE_FLAG } from '../constants'
import type DevToolsDriver from '../devtoolsdriver'
export default async function executeAsyncScript (
this: DevToolsDriver,
{ script, args }: { script: string, args: any[] }

View file

@ -1,3 +1,8 @@
import command from '../scripts/executeScript'
import { transformExecuteArgs, transformExecuteResult } from '../utils'
import { SERIALIZE_PROPERTY, SERIALIZE_FLAG } from '../constants'
import type DevToolsDriver from '../devtoolsdriver'
/**
* The Execute Script command executes a JavaScript function in the context of the
* current browsing context and returns the return value of the function.
@ -8,12 +13,6 @@
* @param {*[]} args an array of JSON values which will be deserialized and passed as arguments to your function
* @return * Either the return value of your script, the fulfillment of the Promise returned by your script, or the error which was the reason for your script's returned Promise's rejection.
*/
import command from '../scripts/executeScript'
import { transformExecuteArgs, transformExecuteResult } from '../utils'
import { SERIALIZE_PROPERTY, SERIALIZE_FLAG } from '../constants'
import type DevToolsDriver from '../devtoolsdriver'
export default async function executeScript (
this: DevToolsDriver,
{ script, args }: { script: string, args: any[] }

View file

@ -1,3 +1,7 @@
import { SUPPORTED_SELECTOR_STRATEGIES } from '../constants'
import { findElement as findElementUtil } from '../utils'
import type DevToolsDriver from '../devtoolsdriver'
/**
* The Find Element command is used to find an element in the current browsing context
* that can be used for future commands.
@ -8,11 +12,6 @@
* @param {string} value the actual selector that will be used to find an element
* @return {Object} A JSON representation of an element object.
*/
import { SUPPORTED_SELECTOR_STRATEGIES } from '../constants'
import { findElement as findElementUtil } from '../utils'
import type DevToolsDriver from '../devtoolsdriver'
export default function findElement (
this: DevToolsDriver,
{ using, value }: { using: string, value: string }

View file

@ -1,3 +1,7 @@
import { SUPPORTED_SELECTOR_STRATEGIES } from '../constants'
import { findElement, getStaleElementError } from '../utils'
import type DevToolsDriver from '../devtoolsdriver'
/**
* The Find Element From Element command is used to find an element from a web element
* in the current browsing context that can be used for future commands.
@ -8,11 +12,6 @@
* @param {string} value the actual selector that will be used to find an element
* @return {Object} A JSON representation of an element object.
*/
import { SUPPORTED_SELECTOR_STRATEGIES } from '../constants'
import { findElement, getStaleElementError } from '../utils'
import type DevToolsDriver from '../devtoolsdriver'
export default async function findElementFromElement (
this: DevToolsDriver,
{ elementId, using, value }: { elementId: string, using: string, value: string }

View file

@ -1,3 +1,7 @@
import { SUPPORTED_SELECTOR_STRATEGIES } from '../constants'
import { findElements as findElementsUtil } from '../utils'
import type DevToolsDriver from '../devtoolsdriver'
/**
* The Find Elements command is used to find elements
* in the current browsing context that can be used for future commands.
@ -8,11 +12,6 @@
* @param {string} value the actual selector that will be used to find an element
* @return {object[]} A (possibly empty) JSON list of representations of an element object.
*/
import { SUPPORTED_SELECTOR_STRATEGIES } from '../constants'
import { findElements as findElementsUtil } from '../utils'
import type DevToolsDriver from '../devtoolsdriver'
export default async function findElements (
this: DevToolsDriver,
{ using, value }: { using: string, value: string }

View file

@ -1,3 +1,7 @@
import { SUPPORTED_SELECTOR_STRATEGIES } from '../constants'
import { findElements, getStaleElementError } from '../utils'
import type DevToolsDriver from '../devtoolsdriver'
/**
* The Find Elements From Element command is used to find elements from a web element
* in the current browsing context that can be used for future commands.
@ -8,11 +12,6 @@
* @param {string} value the actual selector that will be used to find an element
* @return {object[]} A (possibly empty) JSON list of representations of an element object.
*/
import { SUPPORTED_SELECTOR_STRATEGIES } from '../constants'
import { findElements, getStaleElementError } from '../utils'
import type DevToolsDriver from '../devtoolsdriver'
export default async function findElementFromElements (
this: DevToolsDriver,
{ elementId, using, value }: { elementId: string, using: string, value: string }

View file

@ -1,3 +1,5 @@
import type DevToolsDriver from '../devtoolsdriver'
/**
* The Forward command causes the browser to traverse one step forwards
* in the joint session history of the current top-level browsing context.
@ -5,8 +7,6 @@
* @alias browser.forward
* @see https://w3c.github.io/webdriver/#dfn-forward
*/
import type DevToolsDriver from '../devtoolsdriver'
export default async function forward (this: DevToolsDriver) {
delete this.currentFrame
const page = this.getPageHandle()

View file

@ -1,3 +1,9 @@
import findElement from './findElement'
import command from '../scripts/getActiveElement'
import cleanUp from '../scripts/cleanUpSerializationSelector'
import { SERIALIZE_PROPERTY } from '../constants'
import type DevToolsDriver from '../devtoolsdriver'
/**
* Get Active Element returns the active element of the current browsing context’s document element.
*
@ -5,13 +11,6 @@
* @see https://w3c.github.io/webdriver/#dfn-get-active-element
* @return {Object} A JSON representation of an element object.
*/
import findElement from './findElement'
import command from '../scripts/getActiveElement'
import cleanUp from '../scripts/cleanUpSerializationSelector'
import { SERIALIZE_PROPERTY } from '../constants'
import type DevToolsDriver from '../devtoolsdriver'
export default async function getActiveElement (
this: DevToolsDriver
) {

View file

@ -1,3 +1,5 @@
import type DevToolsDriver from '../devtoolsdriver'
/**
* The Get Alert Text command returns the message of the current user prompt.
* If there is no current user prompt, it returns an error.
@ -6,8 +8,6 @@
* @see https://w3c.github.io/webdriver/#dfn-get-alert-text
* @return {string} The message of the user prompt.
*/
import type DevToolsDriver from '../devtoolsdriver'
export default function getAlertText (this: DevToolsDriver) {
if (!this.activeDialog) {
throw new Error('no such alert')

View file

@ -1,3 +1,5 @@
import type DevToolsDriver from '../devtoolsdriver'
/**
* The Get All Cookies command returns all cookies associated with the address
* of the current browsing context’s active document.
@ -6,8 +8,6 @@
* @see https://w3c.github.io/webdriver/#dfn-get-all-cookies
* @return {Object[]} A list of serialized cookies. Each serialized cookie has a number of optional fields which may or may not be returned in addition to `name` and `value`.
*/
import type DevToolsDriver from '../devtoolsdriver'
export default async function getAllCookies (this: DevToolsDriver) {
const page = this.getPageHandle()
return page.cookies()

View file

@ -1,3 +1,7 @@
import command from '../scripts/getElementAttribute'
import { getStaleElementError } from '../utils'
import type DevToolsDriver from '../devtoolsdriver'
/**
* The Get Element Attribute command will return the attribute of a web element.
*
@ -7,11 +11,6 @@
* @param {string} name name of the attribute value to retrieve
* @return {string} The named attribute of the element.
*/
import command from '../scripts/getElementAttribute'
import { getStaleElementError } from '../utils'
import type DevToolsDriver from '../devtoolsdriver'
export default async function getElementAttribute (
this: DevToolsDriver,
{ elementId, name }: { elementId: string, name: string }

View file

@ -1,3 +1,7 @@
import command from '../scripts/getElementCSSValue'
import { getStaleElementError } from '../utils'
import type DevToolsDriver from '../devtoolsdriver'
/**
* The Get Element CSS Value command retrieves the computed value
* of the given CSS property of the given web element.
@ -8,11 +12,6 @@
* @param {string} propertyName name of the CSS property to retrieve
* @return {string} The computed value of the parameter corresponding to property name from the element's style declarations (unless the document type is xml, in which case the return value is simply the empty string).
*/
import command from '../scripts/getElementCSSValue'
import { getStaleElementError } from '../utils'
import type DevToolsDriver from '../devtoolsdriver'
export default async function getElementCSSValue (
this: DevToolsDriver,
{ elementId, propertyName }: { elementId: string, propertyName: string }

View file

@ -1,3 +1,5 @@
import type DevToolsDriver from '../devtoolsdriver'
/**
* Get the computed WAI-ARIA label of an element.
*
@ -6,9 +8,6 @@
* @param {string} elementId the id of an element returned in a previous call to Find Element(s)
* @return {string} The result of computing the WAI-ARIA label of element.
*/
import type DevToolsDriver from '../devtoolsdriver'
export default async function getElementComputedLabel (
this: DevToolsDriver,
{ elementId }: { elementId: string }

View file

@ -1,3 +1,5 @@
import type DevToolsDriver from '../devtoolsdriver'
/**
* Get the computed WAI-ARIA role of an element.
*
@ -6,9 +8,6 @@
* @param {string} elementId the id of an element returned in a previous call to Find Element(s)
* @return {string} The result of computing the WAI-ARIA role of element.
*/
import type DevToolsDriver from '../devtoolsdriver'
export default async function getElementComputedRole (
this: DevToolsDriver,
{ elementId }: { elementId: string }

View file

@ -1,3 +1,6 @@
import { getStaleElementError } from '../utils'
import type DevToolsDriver from '../devtoolsdriver'
/**
* The Get Element Property command will return the result of getting a property of an element.
*
@ -7,10 +10,6 @@
* @param {string} name name of the attribute property to retrieve
* @return {string} The named property of the element, accessed by calling GetOwnProperty on the element object.
*/
import { getStaleElementError } from '../utils'
import type DevToolsDriver from '../devtoolsdriver'
export default async function getElementProperty (
this: DevToolsDriver,
{ elementId, name }: { elementId: string, name: string }

View file

@ -1,3 +1,7 @@
import command from '../scripts/getElementRect'
import { getStaleElementError } from '../utils'
import type DevToolsDriver from '../devtoolsdriver'
/**
* The Get Element Rect command returns the dimensions and coordinates of the given web element.
*
@ -6,11 +10,6 @@
* @param {string} elementId the id of an element returned in a previous call to Find Element(s)
* @return {string} A JSON object representing the position and bounding rect of the element.
*/
import command from '../scripts/getElementRect'
import { getStaleElementError } from '../utils'
import type DevToolsDriver from '../devtoolsdriver'
export default async function getElementRect (
this: DevToolsDriver,
{ elementId }: { elementId: string }

View file

@ -1,3 +1,7 @@
import command from '../scripts/getElementTagName'
import { getStaleElementError } from '../utils'
import type DevToolsDriver from '../devtoolsdriver'
/**
* The Get Element Tag Name command returns the qualified element name of the given web element.
*
@ -6,11 +10,6 @@
* @param {string} elementId the id of an element returned in a previous call to Find Element(s)
* @return {string} The tagName attribute of the element.
*/
import command from '../scripts/getElementTagName'
import { getStaleElementError } from '../utils'
import type DevToolsDriver from '../devtoolsdriver'
export default async function getElementTagName (
this: DevToolsDriver,
{ elementId }: { elementId: string }

View file

@ -1,3 +1,7 @@
import command from '../scripts/getElementText'
import { getStaleElementError } from '../utils'
import type DevToolsDriver from '../devtoolsdriver'
/**
* The Get Element Text command intends to return an element’s text \"as rendered\".
* An element's rendered text is also used for locating a elements
@ -8,11 +12,6 @@
* @param {string} elementId the id of an element returned in a previous call to Find Element(s)
* @return {string} The visible text of the element (including child elements).
*/
import command from '../scripts/getElementText'
import { getStaleElementError } from '../utils'
import type DevToolsDriver from '../devtoolsdriver'
export default async function getElementText (
this: DevToolsDriver,
{ elementId }: { elementId: string }

View file

@ -1,3 +1,5 @@
import type DevToolsDriver from '../devtoolsdriver'
/**
* The Get Named Cookie command returns the cookie with the requested name from the
* associated cookies in the cookie store of the current browsing context's active document.
@ -8,8 +10,6 @@
* @param {string} name name of the cookie to retrieve
* @return {object} A serialized cookie, with name and value fields. There are a number of optional fields like `path`, `domain`, and `expiry-time` which may also be present.
*/
import type DevToolsDriver from '../devtoolsdriver'
export default async function getNamedCookie (
this: DevToolsDriver,
{ name }: { name: string }

View file

@ -1,3 +1,5 @@
import type DevToolsDriver from '../devtoolsdriver'
/**
* The Get Page Source command returns a string serialization of the DOM
* of the current browsing context active document.
@ -6,8 +8,6 @@
* @alias browser.getPageSource
* @return {string} the DOM of the current browsing context active document
*/
import type DevToolsDriver from '../devtoolsdriver'
export default function getPageSource (this: DevToolsDriver) {
const page = this.getPageHandle(true)
return page.content()

View file

@ -1,3 +1,5 @@
import type DevToolsDriver from '../devtoolsdriver'
/**
* The Get Timeouts command gets timeout durations associated with the current session.
*
@ -5,8 +7,6 @@
* @see https://w3c.github.io/webdriver/#dfn-get-timeouts
* @return {Object} Object containing timeout durations for `script`, `pageLoad` and `implicit` timeouts.
*/
import type DevToolsDriver from '../devtoolsdriver'
export default function getTimeouts (this: DevToolsDriver) {
return {
implicit: this.timeouts.get('implicit'),

View file

@ -1,3 +1,5 @@
import type DevToolsDriver from '../devtoolsdriver'
/**
* The Get Title command returns the document title of the current top-level
* browsing context, equivalent to calling `document.title`.
@ -6,8 +8,6 @@
* @see https://w3c.github.io/webdriver/#dfn-get-title
* @return {string} Returns a string which is the same as `document.title` of the current top-level browsing context.
*/
import type DevToolsDriver from '../devtoolsdriver'
export default async function getTitle (this: DevToolsDriver) {
const page = this.getPageHandle(true)
return page.title()

View file

@ -1,3 +1,6 @@
import command from '../scripts/getUrl'
import type DevToolsDriver from '../devtoolsdriver'
/**
* The Get Current URL command returns the URL of the current top-level browsing context
*
@ -5,10 +8,6 @@
* @see https://w3c.github.io/webdriver/#dfn-get-current-url
* @returns {String} current document URL of the top-level browsing context.
*/
import command from '../scripts/getUrl'
import type DevToolsDriver from '../devtoolsdriver'
export default async function getUrl (this: DevToolsDriver) {
const page = this.getPageHandle(true)
return page.$eval('html', command)

View file

@ -1,3 +1,5 @@
import type DevToolsDriver from '../devtoolsdriver'
/**
* The Get Window Handle command returns the window handle for the current top-level browsing context.
* It can be used as an argument to Switch To Window.
@ -6,8 +8,6 @@
* @see https://w3c.github.io/webdriver/#dfn-get-window-handle
* @return {string} Returns a string which is the window handle for the current top-level browsing context.
*/
import type DevToolsDriver from '../devtoolsdriver'
export default async function getWindowHandle (this: DevToolsDriver) {
return this.currentWindowHandle
}

View file

@ -1,3 +1,6 @@
import { v4 as uuidv4 } from 'uuid'
import type DevToolsDriver from '../devtoolsdriver'
/**
* The Get Window Handles command returns a list of window handles
* for every open top-level browsing context.
@ -7,10 +10,6 @@
* @see https://w3c.github.io/webdriver/#dfn-get-window-handles
* @return {string[]} An array which is a list of window handles.
*/
import { v4 as uuidv4 } from 'uuid'
import type DevToolsDriver from '../devtoolsdriver'
export default async function getWindowHandles(this: DevToolsDriver) {
let newPages = await this.browser.pages()

View file

@ -1,3 +1,6 @@
import { DEFAULT_WIDTH, DEFAULT_HEIGHT } from '../constants'
import type DevToolsDriver from '../devtoolsdriver'
/**
* The Get Window Rect command returns the size and position on the screen of the operating system window
* corresponding to the current top-level browsing context.
@ -6,10 +9,6 @@
* @see https://w3c.github.io/webdriver/#dfn-get-window-rect
* @return {object} A JSON representation of a "window rect" object. This has 4 properties: `x`, `y`, `width` and `height`.
*/
import { DEFAULT_WIDTH, DEFAULT_HEIGHT } from '../constants'
import type DevToolsDriver from '../devtoolsdriver'
export default async function getWindowRect (this: DevToolsDriver) {
const page = this.getPageHandle()
const viewport = await page.viewport() || {}

View file

@ -1,3 +1,6 @@
import getElementAttribute from './getElementAttribute'
import type DevToolsDriver from '../devtoolsdriver'
/**
* Is Element Enabled determines if the referenced element is enabled or not.
* This operation only makes sense on form controls.
@ -7,10 +10,6 @@
* @param {string} elementId the id of an element returned in a previous call to Find Element(s)
* @return {string} If the element is in an xml document, or is a disabled form control: `false`, otherwise, `true`.
*/
import getElementAttribute from './getElementAttribute'
import type DevToolsDriver from '../devtoolsdriver'
export default async function isElementEnabled (
this: DevToolsDriver,
{ elementId }: { elementId: string }

View file

@ -1,3 +1,7 @@
import getElementProperty from './getElementProperty'
import getElementTagName from './getElementTagName'
import type DevToolsDriver from '../devtoolsdriver'
/**
* Is Element Selected determines if the referenced element is selected or not.
* This operation only makes sense on input elements of the Checkbox- and Radio Button states,
@ -8,11 +12,6 @@
* @param {string} elementId the id of an element returned in a previous call to Find Element(s)
* @return {boolean} `true` or `false` based on the selected state.
*/
import getElementProperty from './getElementProperty'
import getElementTagName from './getElementTagName'
import type DevToolsDriver from '../devtoolsdriver'
export default async function isElementSelected (
this: DevToolsDriver,
{ elementId }: { elementId: string }

View file

@ -1,3 +1,5 @@
import type DevToolsDriver from '../devtoolsdriver'
/**
* The navigateTo (go) command is used to cause the user agent to navigate the current
* top-level browsing context a new location.
@ -7,8 +9,6 @@
* @param {string} url current top-level browsing context’s active document’s document URL
* @return {string} current document URL of the top-level browsing context.
*/
import type DevToolsDriver from '../devtoolsdriver'
export default async function navigateTo (
this: DevToolsDriver,
{ url }: { url: string }

View file

@ -1,3 +1,11 @@
import os from 'os'
import { v4 as uuidv4 } from 'uuid'
import launch from '../launcher'
import { sessionMap } from '../index'
import type { ExtendedCapabilities } from '../types'
import type DevToolsDriver from '../devtoolsdriver'
/**
* The New Session command creates a new WebDriver session with the endpoint node.
* If the creation fails, a session not created error is returned.
@ -7,17 +15,9 @@
* @param {Object} capabilities An object describing the set of capabilities for the capability processing algorithm
* @return {Object} Object containing sessionId and capabilities of created WebDriver session.
*/
import os from 'os'
import { v4 as uuidv4 } from 'uuid'
import launch from '../launcher'
import { sessionMap } from '../index'
import type DevToolsDriver from '../devtoolsdriver'
export default async function newSession (
this: DevToolsDriver,
{ capabilities }: { capabilities: WebDriver.Capabilities }
{ capabilities }: { capabilities: ExtendedCapabilities }
) {
const browser = await launch(capabilities)
const sessionId = uuidv4()

View file

@ -1,12 +1,3 @@
/**
* The Perform Actions command is used to execute complex user actions.
* See [spec](https://github.com/jlipps/simple-wd-spec#perform-actions) for more details.
*
* @alias browser.performActions
* @see https://w3c.github.io/webdriver/#dfn-perform-actions
* @param {object[]} actions A list of objects, each of which represents an input source and its associated actions.
*/
import { keyDefinitions, KeyInput } from 'puppeteer-core/lib/cjs/puppeteer/common/USKeyboardLayout'
import type { Keyboard, Mouse } from 'puppeteer-core/lib/cjs/puppeteer/common/Input'
@ -38,6 +29,14 @@ interface ActionsParameter {
}
}
/**
* The Perform Actions command is used to execute complex user actions.
* See [spec](https://github.com/jlipps/simple-wd-spec#perform-actions) for more details.
*
* @alias browser.performActions
* @see https://w3c.github.io/webdriver/#dfn-perform-actions
* @param {object[]} actions A list of objects, each of which represents an input source and its associated actions.
*/
export default async function performActions(
this: DevToolsDriver,
{ actions }: { actions: ActionsParameter[] }

View file

@ -1,11 +1,11 @@
import type DevToolsDriver from '../devtoolsdriver'
/**
* The Refresh command causes the browser to reload the page in current top-level browsing context.
*
* @alias browser.refresh
* @see https://w3c.github.io/webdriver/#dfn-refresh
*/
import type DevToolsDriver from '../devtoolsdriver'
export default async function refresh (this: DevToolsDriver) {
delete this.currentFrame

View file

@ -1,15 +1,11 @@
/**
* The Release Actions command is used to release all the keys and pointer buttons that are currently depressed.
* This causes events to be fired as if the state was released by an explicit series of actions.
* It also clears all the internal state of the virtual devices."
* It also clears all the internal state of the virtual devices.
*
* There is no reference implementation for it in Puppeteer so this command will be a NOOP.
*
* @alias browser.performActions
* @see https://w3c.github.io/webdriver/#dfn-release-actions
*/
/**
* there is no reference implementation for it in Puppeteer
*/
export default async function performActions () {
// NYI
}
export default async function performActions () {}

View file

@ -1,3 +1,5 @@
import type DevToolsDriver from '../devtoolsdriver'
/**
* The Send Alert Text command sets the text field of a window.prompt user prompt to the given value.
*
@ -5,8 +7,6 @@
* @see https://w3c.github.io/webdriver/#dfn-send-alert-text
* @param {string} text string to set the prompt to
*/
import type DevToolsDriver from '../devtoolsdriver'
export default async function sendAlertText (
this: DevToolsDriver,
{ text }: { text: string }) {

View file

@ -1,3 +1,5 @@
import type DevToolsDriver from '../devtoolsdriver'
/**
* The Set Timeouts command sets timeout durations associated with the current session.
* The timeouts that can be controlled are listed in the table of session timeouts below.
@ -8,8 +10,6 @@
* @param {number} pageLoad integer in ms for session page load timeout
* @param {number} script integer in ms for session script timeout
*/
import type DevToolsDriver from '../devtoolsdriver'
export default async function setTimeouts (
this: DevToolsDriver,
{ implicit, pageLoad, script }: { implicit: number, pageLoad: number, script: number }

View file

@ -1,3 +1,5 @@
import type DevToolsDriver from '../devtoolsdriver'
/**
* The Set Window Rect command alters the size and the position of the operating system window
* corresponding to the current top-level browsing context.
@ -10,8 +12,6 @@
* @param {number} height the height of the outer dimensions of the top-level browsing context, including browser chrome etc...
* @return {object} A JSON representation of a "window rect" object based on the new window state.
*/
import type DevToolsDriver from '../devtoolsdriver'
export default async function setWindowRect (
this: DevToolsDriver,
params: { width: number, height: number }

View file

@ -1,3 +1,8 @@
import path from 'path'
const puppeteerPath = require.resolve('puppeteer-core')
const puppeteerPkg = require(`${path.dirname(puppeteerPath)}/package.json`)
/**
* The Status command returns information about whether a remote end is in a state
* in which it can create new sessions and can additionally include arbitrary meta information
@ -7,12 +12,6 @@
* @see https://w3c.github.io/webdriver/#dfn-status
* @return {Object} returning an object with the Puppeteer version being used
*/
import path from 'path'
const puppeteerPath = require.resolve('puppeteer-core')
const puppeteerPkg = require(`${path.dirname(puppeteerPath)}/package.json`)
export default async function status () {
return {
message: '',

View file

@ -1,3 +1,11 @@
import type { Page } from 'puppeteer-core/lib/cjs/puppeteer/common/Page'
import type { Frame } from 'puppeteer-core/lib/cjs/puppeteer/common/FrameManager'
import type { ElementReference } from '@wdio/protocols'
import { ELEMENT_KEY } from '../constants'
import { getStaleElementError } from '../utils'
import type DevToolsDriver from '../devtoolsdriver'
/**
* The Switch To Frame command is used to select the current top-level browsing context
* or a child browsing context of the current browsing context to use as the current
@ -7,13 +15,6 @@
* @see https://w3c.github.io/webdriver/#dfn-switch-to-frame
* @param {string|object|null} id one of three possible types: null: this represents the top-level browsing context (i.e., not an iframe), a Number, representing the index of the window object corresponding to a frame, an Element object received using `findElement`.
*/
import type { Page } from 'puppeteer-core/lib/cjs/puppeteer/common/Page'
import type { Frame } from 'puppeteer-core/lib/cjs/puppeteer/common/FrameManager'
import { ELEMENT_KEY } from '../constants'
import { getStaleElementError } from '../utils'
import type DevToolsDriver from '../devtoolsdriver'
export default async function switchToFrame (
this: DevToolsDriver,
{ id }: { id: string }
@ -35,7 +36,7 @@ export default async function switchToFrame (
/**
* switch frame by element ID
*/
const idAsElementReference = id as unknown as WebDriver.ElementReference
const idAsElementReference = id as unknown as ElementReference
if (typeof idAsElementReference[ELEMENT_KEY] === 'string') {
const elementHandle = await this.elementStore.get(idAsElementReference[ELEMENT_KEY])

View file

@ -1,3 +1,7 @@
import type { Page } from 'puppeteer-core/lib/cjs/puppeteer/common/Page'
import type { Frame } from 'puppeteer-core/lib/cjs/puppeteer/common/FrameManager'
import type DevToolsDriver from '../devtoolsdriver'
/**
* The Switch to Parent Frame command sets the current browsing context for future commands
* to the parent of the current browsing context.
@ -5,10 +9,6 @@
* @alias browser.switchToParentFrame
* @see https://w3c.github.io/webdriver/#dfn-switch-to-parent-frame
*/
import type { Page } from 'puppeteer-core/lib/cjs/puppeteer/common/Page'
import type { Frame } from 'puppeteer-core/lib/cjs/puppeteer/common/FrameManager'
import type DevToolsDriver from '../devtoolsdriver'
export default async function switchToParentFrame (this: DevToolsDriver) {
const page = this.getPageHandle(true) as unknown as Frame

View file

@ -1,3 +1,5 @@
import type DevToolsDriver from '../devtoolsdriver'
/**
* The Switch To Window command is used to select the current top-level browsing context
* for the current session, i.e. the one that will be used for processing commands.
@ -6,8 +8,6 @@
* @see https://w3c.github.io/webdriver/#dfn-switch-to-window
* @param {string} handle representing a window handle, should be one of the strings that was returned in a call to getWindowHandles
*/
import type DevToolsDriver from '../devtoolsdriver'
export default async function switchToWindow (
this: DevToolsDriver,
{ handle }: { handle: string }

View file

@ -1,3 +1,6 @@
import { getStaleElementError } from '../utils'
import type DevToolsDriver from '../devtoolsdriver'
/**
* The Take Element Screenshot command takes a screenshot of the visible region
* encompassed by the bounding rectangle of an element.
@ -7,10 +10,6 @@
* @param {string} elementId the id of an element returned in a previous call to Find Element(s)
* @return {string} The base64-encoded PNG image data comprising the screenshot of the visible region of an element’s bounding rectangle after it has been scrolled into view.
*/
import { getStaleElementError } from '../utils'
import type DevToolsDriver from '../devtoolsdriver'
export default async function takeElementScreenshot (
this: DevToolsDriver,
{ elementId }: { elementId: string }

View file

@ -1,3 +1,5 @@
import type DevToolsDriver from '../devtoolsdriver'
/**
* The Take Screenshot command takes a screenshot of the top-level browsing context's viewport.
*
@ -5,8 +7,6 @@
* @see https://w3c.github.io/webdriver/#dfn-take-screenshot
* @return {string} The base64-encoded PNG image data comprising the screenshot of the initial viewport.
*/
import type DevToolsDriver from '../devtoolsdriver'
export default async function takeScreenshot (this: DevToolsDriver) {
const page = this.getPageHandle()
return page.screenshot({

View file

@ -1,4 +1,4 @@
import type { DefaultOptions } from '@wdio/config'
import type { Options } from '@wdio/types'
export const DEFAULT_WIDTH = 1200
export const DEFAULT_HEIGHT = 900
@ -66,7 +66,7 @@ export const BROWSER_TYPE: {
edge: 'edge'
}
export const DEFAULTS: DefaultOptions<WebDriver.Options> = {
export const DEFAULTS: Options.Definition<Options.WebDriver> = {
capabilities: {
type: 'object',
required: true

View file

@ -6,7 +6,7 @@ import logger from '@wdio/logger'
import type { Browser } from 'puppeteer-core/lib/cjs/puppeteer/common/Browser'
import type { Dialog } from 'puppeteer-core/lib/cjs/puppeteer/common/Dialog'
import type { Page } from 'puppeteer-core/lib/cjs/puppeteer/common/Page'
import type WDIOProtocols from '@wdio/protocols'
import type { CommandEndpoint } from '@wdio/protocols'
import ElementStore from './elementstore'
import { validate, sanitizeError } from './utils'
@ -78,7 +78,7 @@ export default class DevToolsDriver {
return require(filePath).default
}
register(commandInfo: WDIOProtocols.CommandEndpoint) {
register(commandInfo: CommandEndpoint) {
const self = this
const { command, ref, parameters, variables = [] } = commandInfo
@ -93,7 +93,7 @@ export default class DevToolsDriver {
* within here you find the webdriver scope
*/
let retries = 0
const wrappedCommand = async function (this: WebdriverIO.BrowserObject, ...args: any[]): Promise<any> {
const wrappedCommand = async function (this: Browser, ...args: any[]): Promise<any> {
await self.checkPendingNavigations()
const params = validate(command, parameters, variables as any, ref, args)
let result

View file

@ -6,12 +6,15 @@ import { v4 as uuidv4 } from 'uuid'
import logger from '@wdio/logger'
import { webdriverMonad, devtoolsEnvironmentDetector } from '@wdio/utils'
import { validateConfig } from '@wdio/config'
import type { CommandEndpoint } from '@wdio/protocols'
import type { Options, Capabilities } from '@wdio/types'
import type { Browser } from 'puppeteer-core/lib/cjs/puppeteer/common/Browser'
import DevToolsDriver from './devtoolsdriver'
import launch from './launcher'
import { DEFAULTS, SUPPORTED_BROWSER, VENDOR_PREFIX } from './constants'
import { getPrototype, patchDebug } from './utils'
import type { ExtendedCapabilities } from './types'
const log = logger('devtools:puppeteer')
@ -23,7 +26,7 @@ patchDebug(log)
export const sessionMap = new Map()
export default class DevTools {
static async newSession (options: WebDriver.Options = {}, modifier?: Function, userPrototype = {}, customCommandWrapper?: Function) {
static async newSession (options: Options.WebDriver, modifier?: Function, userPrototype = {}, customCommandWrapper?: Function) {
const params = validateConfig(DEFAULTS, options)
if (params.logLevel && (!options.logLevels || !(options.logLevels as any)['devtools'])) {
@ -39,7 +42,8 @@ export default class DevTools {
log.info('Initiate new session using the DevTools protocol')
const browser = await launch(params.capabilities as WebDriver.Capabilities)
const requestedCapabilities = { ...params.capabilities }
const browser = await launch(params.capabilities as ExtendedCapabilities)
const pages = await browser.pages()
const driver = new DevToolsDriver(browser, pages)
const sessionId = uuidv4()
@ -51,16 +55,10 @@ export default class DevTools {
*/
type ValueOf<T> = T[keyof T]
const availableVendorPrefixes = Object.values(VENDOR_PREFIX)
const vendorCapPrefix = Object.keys(params.capabilities as WebDriver.Capabilities)
const vendorCapPrefix = Object.keys(params.capabilities as Capabilities.Capabilities)
.find(
(capKey: ValueOf<typeof VENDOR_PREFIX>) => availableVendorPrefixes.includes(capKey)
) as keyof WebDriver.Capabilities
/**
* save original set of capabilities to allow to request the same session again
* (e.g. for reloadSession command in WebdriverIO)
*/
params.requestedCapabilities = { ...params.capabilities }
) as keyof Capabilities.Capabilities
params.capabilities = {
browserName: userAgent.browser.name,
@ -88,7 +86,7 @@ export default class DevTools {
const commandWrapper = (
method: string,
endpoint: string,
commandInfo: WDIOProtocols.CommandEndpoint
commandInfo: CommandEndpoint
) => driver.register(commandInfo)
const protocolCommands = getPrototype(commandWrapper)
const prototype = {
@ -97,11 +95,15 @@ export default class DevTools {
...environmentPrototype
}
const monad = webdriverMonad(params, modifier, prototype)
const monad = webdriverMonad(
{ ...params, requestedCapabilities },
modifier,
prototype
)
return monad(sessionId, customCommandWrapper)
}
static async reloadSession (instance: WebdriverIO.BrowserObject) {
static async reloadSession (instance: any) {
const { session } = sessionMap.get(instance.sessionId)
const browser = await launch(instance.requestedCapabilities)
const pages = await browser.pages()

View file

@ -2,6 +2,7 @@ import { launch as launchChromeBrowser } from 'chrome-launcher'
import puppeteer from 'puppeteer-core'
import logger from '@wdio/logger'
import type { Browser } from 'puppeteer-core/lib/cjs/puppeteer/common/Browser'
import type { Capabilities } from '@wdio/types'
import browserFinder from './finder'
import { getPages } from './utils'
@ -20,31 +21,19 @@ import {
CHANNEL_FIREFOX_TRUNK,
BROWSER_ERROR_MESSAGES
} from './constants'
import type { ExtendedCapabilities } from './types'
const log = logger('devtools')
const DEVICE_NAMES = Object.values(puppeteer.devices).map((device) => device.name)
interface DevToolsOptions {
ignoreDefaultArgs?: string[] | boolean
headless?: boolean,
defaultViewport?: {
width: number,
height: number
}
}
interface ExtendedCapabilities extends WebDriver.Capabilities {
'wdio:devtoolsOptions'?: DevToolsOptions
}
/**
* launches Chrome and returns a Puppeteer browser instance
* @param {object} capabilities session capabilities
* @return {object} puppeteer browser instance
*/
async function launchChrome (capabilities: ExtendedCapabilities) {
const chromeOptions: WebDriver.ChromeOptions = capabilities[VENDOR_PREFIX.chrome] || {}
const chromeOptions: Capabilities.ChromeOptions = capabilities[VENDOR_PREFIX.chrome] || {}
const mobileEmulation = chromeOptions.mobileEmulation || {}
const devtoolsOptions = capabilities['wdio:devtoolsOptions']

View file

@ -0,0 +1,14 @@
import type { Capabilities } from '@wdio/types'
export interface ExtendedCapabilities extends Capabilities.Capabilities {
'wdio:devtoolsOptions'?: DevToolsOptions
}
export interface DevToolsOptions {
ignoreDefaultArgs?: string[] | boolean
headless?: boolean,
defaultViewport?: {
width: number,
height: number
}
}

View file

@ -3,7 +3,7 @@ import path from 'path'
import { execFileSync } from 'child_process'
import logger from '@wdio/logger'
import { commandCallStructure, isValidParameter, getArgumentType, canAccess } from '@wdio/utils'
import { WebDriverProtocol } from '@wdio/protocols'
import { WebDriverProtocol, CommandParameters, CommandPathVariables, ElementReference } from '@wdio/protocols'
import type { Logger } from '@wdio/logger'
import type { ElementHandle } from 'puppeteer-core/lib/cjs/puppeteer/common/JSHandle'
import type { Browser } from 'puppeteer-core/lib/cjs/puppeteer/common/Browser'
@ -19,8 +19,8 @@ const log = logger('devtools')
export const validate = function (
command: string,
parameters: WDIOProtocols.CommandParameters[],
variables: WDIOProtocols.CommandPathVariables[],
parameters: CommandParameters[],
variables: CommandPathVariables[],
ref: string,
args: any[]
) {
@ -101,7 +101,7 @@ export async function findElement (
this: DevToolsDriver,
context: Frame | Page | ElementHandle,
using: string, value: string
): Promise<WebDriver.ElementReference | Error> {
): Promise<ElementReference | Error> {
/**
* implicitly wait for the element if timeout is set
*/
@ -141,7 +141,7 @@ export async function findElements (
this: DevToolsDriver, context: Page | Frame | ElementHandle,
using: string,
value: string
): Promise<WebDriver.ElementReference[]> {
): Promise<ElementReference[]> {
/**
* implicitly wait for the element if timeout is set
*/

View file

@ -13,6 +13,5 @@ Object {
exports[`newSession 2`] = `
Object {
"browserName": "chrome",
"goog:chromeOptions": Object {},
}
`;

View file

@ -4,9 +4,10 @@
"baseUrl": ".",
"outDir": "./build",
"rootDir": "./src",
"types": ["webdriverio", "@wdio/protocols"],
"types": ["@wdio/protocols"],
},
"include": [
"src/**/*"
"src/**/*",
"../../@types"
]
}

View file

@ -7,6 +7,7 @@
"types": ["webdriverio", "@wdio/protocols"]
},
"include": [
"src/**/*"
"src/**/*",
"../../@types"
]
}

View file

@ -1,55 +0,0 @@
declare namespace AllureReporter {
type StepStatus = 'passed' | 'failed' | 'broken' | 'canceled' | 'skipped'
function addFeature(
featureName: string
): void;
function addLabel(
name: string,
value: string
): void;
function addSeverity(
severity: string
): void;
function addIssue(
issue: string
): void;
function addTestId(
testId: string
): void;
function addStory(
storyName: string
): void;
function addEnvironment(
name: string,
value: string
): void;
function addDescription(
description: string,
descriptionType: string
): void;
function addAttachment(
name: string,
content: any,
mimeType?: string
): void;
function startStep(
title: string
): void;
function endStep(
status?: StepStatus
): void;
function addStep(
title: string,
attachmentObject?: object,
status?: string
): void;
function addArgument(
name: string,
value: string
): void;
}
declare module "@wdio/allure-reporter" {
export default AllureReporter
}

View file

@ -9,7 +9,7 @@
"engines": {
"node": ">=12.0.0"
},
"types": "./allure-reporter.d.ts",
"types": "./build/index.d.ts",
"repository": {
"type": "git",
"url": "git://github.com/webdriverio/webdriverio.git"
@ -25,6 +25,7 @@
},
"dependencies": {
"@wdio/reporter": "6.11.0",
"@wdio/types": "^6.10.6",
"allure-js-commons": "^1.3.2",
"strip-ansi": "^6.0.0"
},

View file

@ -1,100 +0,0 @@
// Allure is providing its own types in v2, but there's too many changes,
// so this is temporarily copied here to customize typings with allure 1
// Type definitions for allure-js-commons 0.0
// Project: https://github.com/allure-framework/allure-js
// Definitions by: Denis Artyuhovich <https://github.com/zaqqaz>
// Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped
// TypeScript Version: 2.7
declare module 'allure-js-commons/beans/step' {
class Step {}
export = Step
}
declare module 'allure-js-commons' {
class Allure {
constructor();
setOptions(options: Allure.Options): void;
getCurrentSuite(): Allure.Suite;
getCurrentTest(): Allure.Test;
startSuite(suiteName: string, timestamp?: number): void;
endSuite(timestamp?: number): void;
startCase(testName: string, timestamp?: number): void;
endCase(status: Allure.Status, err?: {}, timestamp?: number): void;
startStep(stepName?: string, timestamp?: number): void;
endStep(status: Allure.Status, timestamp?: number): void;
setDescription(description: string, timestamp?: number): void;
addAttachment(attachmentName: string, buffer: any, type?: string): void;
pendingCase(testName: string, timestamp?: number): void;
}
export = Allure;
namespace Allure {
interface Options {
targetDir: string;
}
// type Status = "passed" | "pending" | "skipped" | "failed" | "broken";
type Status = 'passed' | 'pending' | 'skipped' | 'failed' | 'broken' | 'canceled';
enum TYPE {
TEXT = 'text',
HTML = 'html',
MARKDOWN = 'markdown'
}
class Suite {
currentStep: Allure.Step;
testcases: string[];
name: string;
constructor(name: string, timestamp?: number);
end(timestamp?: number): void;
hasTests(): boolean;
addTest(test: Test): boolean;
toXML(): string;
}
class Test {
steps: Allure.Step[];
name: string;
constructor(name: string, timestamp?: number);
setDescription(description: string | undefined, type?: TYPE): void;
addLabel(name: string, value?: string): void;
addParameter(kind: any, name: string, value?: string): void;
addStep(step: Step): void;
addAttachment(attachment: Attachment): void;
end(status: Status, error: Error, timestamp?: number): void;
toXML(): string;
}
class Description {
constructor(value: string, type: TYPE);
toXML(): string;
}
class Step {
attachments: Attachment[];
constructor(name: string, timestamp?: number);
addStep(step: Step): void;
addAttachment(attachment: Attachment): void;
end(status: Status, error: Error, timestamp?: number): void;
toXML(): string;
}
class Attachment {
constructor(title: string, source: any, size: number, mime: string);
addStep(step: Step): void;
addAttachment(attachment: Attachment): void;
end(status: Status, error: Error, timestamp?: number): void;
toXML(): string;
}
}
}

View file

@ -1,44 +1,44 @@
import Allure from 'allure-js-commons'
export const PASSED = 'passed'
export const FAILED = 'failed'
export const BROKEN = 'broken'
export const PENDING = 'pending'
export const CANCELED = 'canceled'
export const SKIPPED = 'skipped'
const testStatuses: Record<string, Allure.Status> = {
PASSED,
FAILED,
BROKEN,
PENDING
} as const
const stepStatuses: Record<string, Allure.Status> = {
PASSED,
FAILED,
BROKEN,
CANCELED,
SKIPPED
} as const
const events = {
addLabel: 'allure:addLabel',
addFeature: 'allure:addFeature',
addStory: 'allure:addStory',
addSeverity: 'allure:addSeverity',
addIssue: 'allure:addIssue',
addTestId: 'allure:addTestId',
addEnvironment: 'allure:addEnvironment',
addDescription: 'allure:addDescription',
addAttachment: 'allure:addAttachment',
startStep: 'allure:startStep',
endStep: 'allure:endStep',
addStep: 'allure:addStep',
addArgument: 'allure:addArgument'
}
const mochaEachHooks = ['"before each" hook', '"after each" hook']
const mochaAllHooks = ['"before all" hook', '"after all" hook']
const linkPlaceholder = '{}'
export { testStatuses, stepStatuses, events, mochaEachHooks, mochaAllHooks, linkPlaceholder }
import type { Status } from './types'
export const PASSED = 'passed'
export const FAILED = 'failed'
export const BROKEN = 'broken'
export const PENDING = 'pending'
export const CANCELED = 'canceled'
export const SKIPPED = 'skipped'
const testStatuses: Record<string, Status> = {
PASSED,
FAILED,
BROKEN,
PENDING
} as const
const stepStatuses: Record<string, Status> = {
PASSED,
FAILED,
BROKEN,
CANCELED,
SKIPPED
} as const
const events = {
addLabel: 'allure:addLabel',
addFeature: 'allure:addFeature',
addStory: 'allure:addStory',
addSeverity: 'allure:addSeverity',
addIssue: 'allure:addIssue',
addTestId: 'allure:addTestId',
addEnvironment: 'allure:addEnvironment',
addDescription: 'allure:addDescription',
addAttachment: 'allure:addAttachment',
startStep: 'allure:startStep',
endStep: 'allure:endStep',
addStep: 'allure:addStep',
addArgument: 'allure:addArgument'
} as const
const mochaEachHooks = ['"before each" hook', '"after each" hook'] as const
const mochaAllHooks = ['"before all" hook', '"after all" hook'] as const
const linkPlaceholder = '{}'
export { testStatuses, stepStatuses, events, mochaEachHooks, mochaAllHooks, linkPlaceholder }

File diff suppressed because it is too large Load diff

View file

@ -1,85 +1,103 @@
import { WDIOReporterBaseOptions } from '@wdio/reporter'
import Allure from 'allure-js-commons'
/**
* When you add a new option, please also update the docs at ./packages/wdio-allure-reporter/README.md
*/
export interface AllureReporterOptions extends WDIOReporterBaseOptions {
export interface AllureReporterOptions {
/**
* defaults to `./allure-results`. After a test run is complete, you will find that this directory
* has been populated with an `.xml` file for each spec, plus a number of `.txt` and `.png`
* files and other attachments.
*/
outputDir?: string;
outputDir?: string
/**
* optional parameter (`false` by default), set it to true in order to change the report hierarchy
* when using cucumber. Try it for yourself and see how it looks.
*/
useCucumberStepReporter?: boolean;
useCucumberStepReporter?: boolean
/**
* optional parameter (`false` by default), set it to true in order to not fetch the `before/after`
* stacktrace/screenshot/result hooks into the Allure Reporter.
*/
disableMochaHooks?: boolean;
disableMochaHooks?: boolean
/**
* optional parameter(`false` by default), in order to log only custom steps to the reporter.
*/
disableWebdriverStepsReporting?: boolean;
disableWebdriverStepsReporting?: boolean
/**
* optional parameter(`false` by default), in order to not attach screenshots to the reporter.
*/
disableWebdriverScreenshotsReporting?: boolean;
disableWebdriverScreenshotsReporting?: boolean
/**
* optional parameter, in order to specify the issue link pattern. Reporter will replace `{}` placeholder
* with value specified in `addIssue(value)` call parameter.
* Example `https://example.org/issue/{}`
*/
issueLinkTemplate?: string;
issueLinkTemplate?: string
/**
* optional parameter, in order to specify the tms link pattern. Reporter will replace `{}` placeholder
* with value specified in `addTestId(value)` call parameter. Example `https://example.org/tms/{}`
*/
tmsLinkTemplate?: string;
tmsLinkTemplate?: string
}
export interface AddLabelEventArgs {
name: string;
value: string;
name: string
value: string
}
export interface AddStoryEventArgs {
storyName: string;
storyName: string
}
export interface AddFeatureEventArgs {
featureName: string;
featureName: string
}
export interface AddSeverityEventArgs {
severity: string;
severity: string
}
export interface AddIssueEventArgs {
issue: string;
issue: string
}
export interface AddTestIdEventArgs {
testId: string;
testId: string
}
export interface AddEnvironmentEventArgs {
name: string;
value: string;
name: string
value: string
}
enum TYPE {
TEXT = 'text',
HTML = 'html',
MARKDOWN = 'markdown'
}
export interface AddDescriptionEventArgs {
description?: string;
descriptionType?: Allure.TYPE;
description?: string
descriptionType?: TYPE
}
export interface AddAttachmentEventArgs {
name: string;
content: string | object;
type: string;
name: string
content: string | object
type: string
}
export interface Step {
attachments: Attachment[];
addStep(step: Step): void;
addAttachment(attachment: Attachment): void;
end(status: Status, error: Error, timestamp?: number): void;
toXML(): string;
}
export type Status = 'passed' | 'pending' | 'skipped' | 'failed' | 'broken' | 'canceled';
export interface Attachment {
addStep(step: Step): void;
addAttachment(attachment: Attachment): void;
end(status: Status, error: Error, timestamp?: number): void;
toXML(): string;
}

View file

@ -1,110 +1,112 @@
import process from 'process'
import CompoundError from './compoundError'
import { mochaEachHooks, mochaAllHooks, linkPlaceholder } from './constants'
import stripAnsi from 'strip-ansi'
import Allure from 'allure-js-commons'
import { HookStats, TestStats } from '@wdio/reporter'
/**
* Get allure test status by TestStat object
* @param test {Object} - TestStat object
* @param config {Object} - wdio config object
* @private
*/
export const getTestStatus = (test: TestStats | HookStats, config: WebdriverIO.Config) : Allure.Status => {
if (config.framework === 'jasmine') {
return 'failed'
}
if (test.error) {
if (test.error.name && test.error.message) {
const message = test.error.message.trim()
return (test.error.name === 'AssertionError' || message.includes('Expect')) ? 'failed' : 'broken'
}
if (test.error.name) {
return test.error.name === 'AssertionError' ? 'failed' : 'broken'
}
if (test.error.stack) {
const stackTrace = test.error.stack.trim()
return (stackTrace.startsWith('AssertionError') || stackTrace.includes('Expect')) ? 'failed' : 'broken'
}
}
return 'broken'
}
/**
* Check is object is empty
* @param object {Object}
* @private
*/
export const isEmpty = (object: any) => !object || Object.keys(object).length === 0
/**
* Is mocha beforeEach / afterEach hook
* @param title {String} - hook title
* @returns {boolean}
* @private
*/
export const isMochaEachHooks = (title: string) => mochaEachHooks.some(hook => title.includes(hook))
/**
* Is mocha beforeAll / afterAll hook
* @param title {String} - hook title
* @returns {boolean}
* @private
*/
export const isMochaAllHooks = (title: string) => mochaAllHooks.some(hook => title.includes(hook))
/**
* Call reporter
* @param {string} event - event name
* @param {Object} msg - event payload
* @private
*/
export const tellReporter = (event: string, msg: any = {}) => {
// Node 14 typings does not accept string in process.emit, but allow string in process.on
process.emit(event as any, msg)
}
/**
* Properly format error from different test runners
* @param {Object} test - TestStat object
* @returns {Object} - error object
* @private
*/
export const getErrorFromFailedTest = (test: TestStats | HookStats) : Error | CompoundError | undefined => {
if (test.errors && Array.isArray(test.errors)) {
for (let i = 0; i < test.errors.length; i += 1){
if (test.errors[i].message) test.errors[i].message = stripAnsi(test.errors[i].message)
if (test.errors[i].stack) test.errors[i].stack = stripAnsi(test.errors[i].stack!)
}
return test.errors.length === 1 ? test.errors[0] : new CompoundError(...test.errors as Error[])
}
if (test.error) {
if (test.error.message) test.error.message = stripAnsi(test.error.message)
if (test.error.stack) test.error.stack = stripAnsi(test.error.stack)
}
return test.error
}
/**
* Substitute task id to link template
* @param {string} template - link template
* @param {string} id - task id
* @returns {string} - link after substitution
* @private
*/
export const getLinkByTemplate = (template: string | undefined, id: string) => {
if (typeof template !== 'string') {
return id
}
if (!template.includes(linkPlaceholder)) {
throw Error(`The link template "${template}" must contain ${linkPlaceholder} substring.`)
}
return template.replace(linkPlaceholder, id)
}
import process from 'process'
import stripAnsi from 'strip-ansi'
import { HookStats, TestStats } from '@wdio/reporter'
import type { Options } from '@wdio/types'
import CompoundError from './compoundError'
import { mochaEachHooks, mochaAllHooks, linkPlaceholder } from './constants'
import type { Status } from './types'
/**
* Get allure test status by TestStat object
* @param test {Object} - TestStat object
* @param config {Object} - wdio config object
* @private
*/
export const getTestStatus = (test: TestStats | HookStats, config?: Options.Testrunner) : Status => {
if (config && config.framework === 'jasmine') {
return 'failed'
}
if (test.error) {
if (test.error.name && test.error.message) {
const message = test.error.message.trim()
return (test.error.name === 'AssertionError' || message.includes('Expect')) ? 'failed' : 'broken'
}
if (test.error.name) {
return test.error.name === 'AssertionError' ? 'failed' : 'broken'
}
if (test.error.stack) {
const stackTrace = test.error.stack.trim()
return (stackTrace.startsWith('AssertionError') || stackTrace.includes('Expect')) ? 'failed' : 'broken'
}
}
return 'broken'
}
/**
* Check is object is empty
* @param object {Object}
* @private
*/
export const isEmpty = (object: any) => !object || Object.keys(object).length === 0
/**
* Is mocha beforeEach / afterEach hook
* @param title {String} - hook title
* @returns {boolean}
* @private
*/
export const isMochaEachHooks = (title: string) => mochaEachHooks.some(hook => title.includes(hook))
/**
* Is mocha beforeAll / afterAll hook
* @param title {String} - hook title
* @returns {boolean}
* @private
*/
export const isMochaAllHooks = (title: string) => mochaAllHooks.some(hook => title.includes(hook))
/**
* Call reporter
* @param {string} event - event name
* @param {Object} msg - event payload
* @private
*/
export const tellReporter = (event: string, msg: any = {}) => {
// Node 14 typings does not accept string in process.emit, but allow string in process.on
process.emit(event as any, msg)
}
/**
* Properly format error from different test runners
* @param {Object} test - TestStat object
* @returns {Object} - error object
* @private
*/
export const getErrorFromFailedTest = (test: TestStats | HookStats) : Error | CompoundError | undefined => {
if (test.errors && Array.isArray(test.errors)) {
for (let i = 0; i < test.errors.length; i += 1){
if (test.errors[i].message) test.errors[i].message = stripAnsi(test.errors[i].message)
if (test.errors[i].stack) test.errors[i].stack = stripAnsi(test.errors[i].stack!)
}
return test.errors.length === 1 ? test.errors[0] : new CompoundError(...test.errors as Error[])
}
if (test.error) {
if (test.error.message) test.error.message = stripAnsi(test.error.message)
if (test.error.stack) test.error.stack = stripAnsi(test.error.stack)
}
return test.error
}
/**
* Substitute task id to link template
* @param {string} template - link template
* @param {string} id - task id
* @returns {string} - link after substitution
* @private
*/
export const getLinkByTemplate = (template: string | undefined, id: string) => {
if (typeof template !== 'string') {
return id
}
if (!template.includes(linkPlaceholder)) {
throw Error(`The link template "${template}" must contain ${linkPlaceholder} substring.`)
}
return template.replace(linkPlaceholder, id)
}

View file

@ -3,10 +3,10 @@
"compilerOptions": {
"baseUrl": ".",
"outDir": "./build",
"rootDir": "./src",
"types": ["webdriverio"]
"rootDir": "./src"
},
"include": [
"src/**/*"
"src/**/*",
"../../@types"
]
}

View file

@ -3,10 +3,10 @@
"compilerOptions": {
"baseUrl": ".",
"outDir": "./build",
"rootDir": "./src",
"types": ["webdriverio"]
"rootDir": "./src"
},
"include": [
"src/**/*"
"src/**/*",
"../../@types"
]
}

View file

@ -29,6 +29,7 @@
"@types/fs-extra": "^9.0.4",
"@wdio/config": "6.12.1",
"@wdio/logger": "6.10.10",
"@wdio/types": "^6.10.6",
"fs-extra": "^9.0.0",
"param-case": "^3.0.0"
},
@ -38,5 +39,5 @@
"publishConfig": {
"access": "public"
},
"types": "appium-service.d.ts"
"types": "./build/index.d.ts"
}

View file

@ -1,51 +0,0 @@
declare module WebdriverIO {
interface ServiceOption extends AppiumServiceConfig {}
}
declare module WebDriver {
interface DesiredCapabilities extends AppiumSessionCapabilities {}
}
type ArgValue = string | number | boolean | null
type AppiumServerArguments = {
[capability: string]: any
/**
* Port to listen on
*/
port?: number | string
basePath?: string
}
interface AppiumSessionCapabilities {
/**
* Default session parameters
*/
port?: number
protocol?: string
hostname?: string
path?: string
}
type KeyValueArgs = {
[key: string]: ArgValue
}
interface AppiumServiceConfig {
/**
* Path where all logs from the Appium server should be stored.
*/
logPath?: string;
/**
* To use your own installation of Appium, e.g. globally installed, specify the command which should be started.
*/
command?: string;
/**
* Map of arguments for the Appium server, passed directly to `appium`.
*/
args?: AppiumServerArguments | Array<string>
}
type Config = {
outputDir?: string
[key: string]: any
}

View file

@ -4,3 +4,12 @@ import AppiumLauncher from './launcher'
export default class AppiumService {}
export const launcher = AppiumLauncher
export * from './types'
import { AppiumServiceConfig } from './types'
declare global {
namespace WebdriverIO {
interface ServiceOption extends AppiumServiceConfig {}
}
}

View file

@ -2,9 +2,12 @@ import logger from '@wdio/logger'
import { ChildProcessByStdio, spawn } from 'child_process'
import { createWriteStream, ensureFileSync } from 'fs-extra'
import { promisify } from 'util'
import { getFilePath, formatCliArgs } from './utils'
import { Readable } from 'stream'
import { isCloudCapability } from '@wdio/config'
import type { Services, Capabilities, Options } from '@wdio/types'
import { getFilePath, formatCliArgs } from './utils'
import type { AppiumServerArguments, AppiumServiceConfig } from './types'
const log = logger('@wdio/appium-service')
const DEFAULT_LOG_FILENAME = 'wdio-appium.log'
@ -16,32 +19,23 @@ const DEFAULT_CONNECTION = {
path: '/'
}
export default class AppiumLauncher implements WebdriverIO.ServiceInstance {
export default class AppiumLauncher implements Services.ServiceInstance {
private readonly _logPath?: string
private readonly _appiumCliArgs: Array<string> = []
private readonly _capabilities: Array<WebDriver.DesiredCapabilities>
private readonly _appiumCliArgs: string[] = []
private readonly _args: AppiumServerArguments
private _command: string
private _process?: ChildProcessByStdio<null, Readable, Readable>
constructor(
private _options: WebdriverIO.ServiceOption,
capabilities:
Array<WebDriver.DesiredCapabilities> | {[capabilitiy: string]: WebDriver.DesiredCapabilities } = {},
private _config?: Config
private _options: AppiumServiceConfig,
private _capabilities: Capabilities.RemoteCapabilities,
private _config?: Options.Testrunner
) {
/**
* Convert capability object to Array of capabilities
*/
this._capabilities = Array.isArray(capabilities)
? capabilities
: Object.values(capabilities)
this._args = {
basePath: DEFAULT_CONNECTION.path,
...(this._options.args || {})
}
this._logPath = _options.logPath || _config?.outputDir
this._logPath = _options.logPath || this._config?.outputDir
this._command = this._getCommand(_options.command)
}
@ -71,8 +65,26 @@ export default class AppiumLauncher implements WebdriverIO.ServiceInstance {
* to Appium server
*/
private _setCapabilities() {
/**
* Multiremote sessions
*/
if (!Array.isArray(this._capabilities)) {
for (const [, capability] of Object.entries(this._capabilities)) {
const cap = (capability.capabilities as Capabilities.W3CCapabilities) || capability
const c = (cap as Capabilities.W3CCapabilities).alwaysMatch || cap
!isCloudCapability(c) && Object.assign(
capability,
DEFAULT_CONNECTION,
'port' in this._args ? { port: this._args.port } : {},
{ path: this._args.basePath },
{ ...capability }
)
}
return
}
this._capabilities.forEach(
(cap) => !isCloudCapability(cap) && Object.assign(
(cap) => !isCloudCapability((cap as Capabilities.W3CCapabilities).alwaysMatch || cap) && Object.assign(
cap,
DEFAULT_CONNECTION,
'port' in this._args ? { port: this._args.port } : {},

View file

@ -1,14 +1,4 @@
declare module WebdriverIO {
interface ServiceOption extends AppiumServiceConfig {}
}
declare module WebDriver {
interface DesiredCapabilities extends AppiumSessionCapabilities {}
}
type ArgValue = string | number | boolean | null
type AppiumServerArguments = {
export type AppiumServerArguments = {
[capability: string]: any
/**
* Port to listen on
@ -16,7 +6,8 @@ type AppiumServerArguments = {
port?: number | string
basePath?: string
}
interface AppiumSessionCapabilities {
export interface AppiumSessionCapabilities {
/**
* Default session parameters
*/
@ -26,26 +17,20 @@ interface AppiumSessionCapabilities {
path?: string
}
type KeyValueArgs = {
[key: string]: ArgValue
}
interface AppiumServiceConfig {
export interface AppiumServiceConfig {
/**
* Path where all logs from the Appium server should be stored.
*/
logPath?: string;
logPath?: string
/**
* To use your own installation of Appium, e.g. globally installed, specify the command which should be started.
*/
command?: string;
command?: string
/**
* Map of arguments for the Appium server, passed directly to `appium`.
*/
args?: AppiumServerArguments | Array<string>
}
type Config = {
outputDir?: string
[key: string]: any
}
export type ArgValue = string | number | boolean | null
export type KeyValueArgs = { [key: string]: ArgValue }

View file

@ -1,6 +1,8 @@
import { basename, join, resolve } from 'path'
import { paramCase } from 'param-case'
import { ArgValue, KeyValueArgs } from './types'
const FILE_EXTENSION_REGEX = /\.[0-9a-z]+$/i
/**
@ -28,7 +30,7 @@ export function formatCliArgs(args: KeyValueArgs | ArgValue[]): string[] {
const cliArgs = []
for (const key in args) {
let value: ArgValue | ArgValue[] = args[key]
let value: ArgValue | ArgValue[] = args[key]
// If the value is false or null the argument is discarded
if ((typeof value === 'boolean' && !value) || value === null) {
continue

View file

@ -3,6 +3,7 @@ import childProcess from 'child_process'
import fs from 'fs-extra'
import { mocked } from 'ts-jest/utils'
import path from 'path'
import type { Capabilities, Options } from '@wdio/types'
jest.mock('child_process', () => ({
spawn: jest.fn(),
@ -81,8 +82,8 @@ describe('Appium launcher', () => {
command:'path/to/my_custom_appium',
args: { foo: 'bar' }
}
const capabilities = [{ port: 1234 }] as WebDriver.DesiredCapabilities[]
const launcher = new AppiumLauncher(options, capabilities, {})
const capabilities = [{ port: 1234, capabilities: [] }] as (Capabilities.DesiredCapabilities & Options.WebDriver)[]
const launcher = new AppiumLauncher(options, capabilities, {} as any)
await launcher.onPrepare()
expect(launcher['_process']).toBeInstanceOf(MockProcess)
@ -104,11 +105,11 @@ describe('Appium launcher', () => {
command: 'path/to/my_custom_appium',
args: { foo: 'bar' }
}
const capabilities = {
browserA: { port: 1234 } as WebDriver.DesiredCapabilities,
browserB: {} as WebDriver.DesiredCapabilities
const capabilities: Capabilities.MultiRemoteCapabilities = {
browserA: { port: 1234, capabilities: {} },
browserB: { capabilities: {} }
}
const launcher = new AppiumLauncher(options, capabilities, {})
const launcher = new AppiumLauncher(options, capabilities, {} as any)
await launcher.onPrepare()
expect(capabilities.browserA.protocol).toBe('http')
expect(capabilities.browserA.hostname).toBe('localhost')
@ -126,11 +127,11 @@ describe('Appium launcher', () => {
args : { foo : 'foo' },
installArgs : { bar : 'bar' },
}
const capabilities = {
browserA: { port: 1234 } as WebDriver.DesiredCapabilities,
browserB: { port: 4321, capabilities: { 'bstack:options': {} } } as WebDriver.DesiredCapabilities
const capabilities: Capabilities.MultiRemoteCapabilities = {
browserA: { port: 1234, capabilities: {} },
browserB: { port: 4321, capabilities: { 'bstack:options': {} } }
}
const launcher = new AppiumLauncher(options, capabilities, {})
const launcher = new AppiumLauncher(options, capabilities, {} as any)
launcher['_redirectLogStream'] = jest.fn()
await launcher.onPrepare()
expect(capabilities.browserA.protocol).toBe('http')
@ -149,8 +150,8 @@ describe('Appium launcher', () => {
command: 'path/to/my_custom_appium',
args: { foo: 'bar', port: 1234 }
}
const capabilities = [{} as WebDriver.DesiredCapabilities]
const launcher = new AppiumLauncher(options, capabilities, {})
const capabilities = [{} as Capabilities.DesiredCapabilities]
const launcher = new AppiumLauncher(options, capabilities, {} as any)
launcher['_startAppium'] = jest.fn().mockImplementation(
(cmd, args, cb) => cb(null, new MockProcess()))
await launcher.onPrepare()
@ -174,8 +175,8 @@ describe('Appium launcher', () => {
command: 'path/to/my_custom_appium',
args: { foo: 'bar', port: 1234, basePath: '/foo/bar' }
}
const capabilities = [{ port: 4321 } as WebDriver.DesiredCapabilities]
const launcher = new AppiumLauncher(options, capabilities, {})
const capabilities = [{ port: 4321 } as Capabilities.DesiredCapabilities]
const launcher = new AppiumLauncher(options, capabilities, {} as any)
launcher['_startAppium'] = jest.fn().mockImplementation(
(cmd, args, cb) => cb(null, new MockProcess()))
await launcher.onPrepare()
@ -203,7 +204,7 @@ describe('Appium launcher', () => {
logPath: './',
command: 'path/to/my_custom_appium',
args: { foo: 'bar' }
}, [], {})
}, [], {} as any)
await launcher.onPrepare()
expect(launcher['_command']).toBe('cmd')
@ -219,7 +220,7 @@ describe('Appium launcher', () => {
logPath: './',
command: 'path/to/my_custom_appium',
args: { foo: 'bar' }
}, [], {})
}, [], {} as any)
await launcher.onPrepare()
expect(launcher['_command']).toBe('path/to/my_custom_appium')
@ -235,7 +236,7 @@ describe('Appium launcher', () => {
logPath: './',
command: 'path/to/my_custom_appium',
args: { foo: 'bar' }
}, [], {})
}, [], {} as any)
await launcher.onPrepare()
expect(launcher['_command']).toBe('path/to/my_custom_appium')
@ -243,7 +244,7 @@ describe('Appium launcher', () => {
})
test('should set correct config properties when empty', async () => {
const launcher = new AppiumLauncher({}, [], {})
const launcher = new AppiumLauncher({}, [], {} as any)
await launcher.onPrepare()
expect(launcher['_logPath']).toBe(undefined)
@ -255,12 +256,12 @@ describe('Appium launcher', () => {
})
test('should start Appium', async () => {
const launcher = new AppiumLauncher({ args: { superspeed: true } }, [], {})
const launcher = new AppiumLauncher({ args: { superspeed: true } }, [], {} as any)
await launcher.onPrepare()
})
test('should fail if Appium exits', async () => {
const launcher = new AppiumLauncher({}, [], {})
const launcher = new AppiumLauncher({}, [], {} as any)
childProcessMock.spawn.mockReturnValue(new MockFailingProcess(1) as unknown as childProcess.ChildProcess)
let error
@ -274,7 +275,7 @@ describe('Appium launcher', () => {
})
test('should fail and error message if Appium already runs', async () => {
const launcher = new AppiumLauncher({}, [], {})
const launcher = new AppiumLauncher({}, [], {} as any)
childProcessMock.spawn.mockReturnValue(new MockFailingProcess(2) as unknown as childProcess.ChildProcess)
let error
@ -289,7 +290,7 @@ describe('Appium launcher', () => {
})
test('should fail with Appium error message', async () => {
const launcher = new AppiumLauncher({}, [], {})
const launcher = new AppiumLauncher({}, [], {} as any)
childProcessMock.spawn.mockReturnValue(new MockCustomFailingProcess(2) as unknown as childProcess.ChildProcess)
let error
@ -305,7 +306,7 @@ describe('Appium launcher', () => {
describe('onComplete', () => {
test('should call process.kill', async () => {
const launcher = new AppiumLauncher({}, [], {})
const launcher = new AppiumLauncher({}, [], {} as any)
await launcher.onPrepare()
launcher['_process']!.kill = jest.fn()
launcher.onComplete()
@ -313,7 +314,7 @@ describe('Appium launcher', () => {
})
test('should not call process.kill', () => {
const launcher = new AppiumLauncher({}, [], {})
const launcher = new AppiumLauncher({}, [], {} as any)
expect(launcher['_process']).toBe(undefined)
launcher.onComplete()
expect(launcher['_process']).toBe(undefined)
@ -322,14 +323,14 @@ describe('Appium launcher', () => {
describe('_redirectLogStream', () => {
test('should not write output to file', async () => {
const launcher = new AppiumLauncher({}, [], {})
const launcher = new AppiumLauncher({}, [], {} as any)
launcher['_redirectLogStream'] = jest.fn()
await launcher.onPrepare()
expect(launcher['_redirectLogStream']).not.toBeCalled()
})
test('should write output to file', async () => {
const launcher = new AppiumLauncher({ logPath: './' }, [], {})
const launcher = new AppiumLauncher({ logPath: './' }, [], {} as any)
await launcher.onPrepare()
expect(fsMocked.createWriteStream.mock.calls[0][0]).toBe('/some/file/path')

View file

@ -6,6 +6,7 @@
"rootDir": "./src"
},
"include": [
"src/**/*"
"src/**/*",
"../../@types"
]
}

View file

@ -6,6 +6,7 @@
"rootDir": "./src"
},
"include": [
"src/**/*"
"src/**/*",
"../../@types"
]
}

View file

@ -1,53 +0,0 @@
/// <reference types="@applitools/visual-grid-client"/>
declare namespace WebdriverIO {
interface ServiceOption extends ApplitoolsConfig {}
interface Browser extends ApplitoolsBrowser {}
}
interface ProxySettings {
url: string | boolean;
username: string;
password: string;
isHttpOnly: boolean;
}
interface ApplitoolsConfig {
/**
* Applitools API key to be used. Can be passed via wdio config or via environment
* variable `APPLITOOLS_KEY`
*/
key?: string;
/**
* Applitools server URL to be used.
*/
serverUrl?: string;
/**
* Viewport with which the screenshots should be taken.
*/
viewport?: {
width?: number;
height?: number;
};
/**
* Use proxy for http/https connections with Applitools.
*/
proxy?: ProxySettings;
}
interface ApplitoolsBrowser {
takeSnapshot(title: string): void;
takeRegionSnapshot(
title: string,
region: Eyes.Check.Region | WebdriverIO.Element | string,
frame?: WebdriverIO.Element | string
): void;
}
interface ApplitoolsBrowserAsync {
takeSnapshot(title: string): Promise<void>;
takeRegionSnapshot(
title: string,
region: Eyes.Check.Region | WebdriverIO.Element | string,
frame?: WebdriverIO.Element | string
): Promise<void>;
}

View file

@ -27,7 +27,9 @@
},
"dependencies": {
"@applitools/eyes-webdriverio": "^5.8.4",
"@wdio/logger": "6.10.10"
"@wdio/logger": "6.10.10",
"@wdio/types": "^6.10.6",
"webdriverio": "^6.11.3"
},
"peerDependencies": {
"@wdio/cli": "^6.0.1"
@ -35,5 +37,5 @@
"publishConfig": {
"access": "public"
},
"types": "applitools-service.d.ts"
"types": "./build/index.d.ts"
}

View file

@ -1 +0,0 @@
../../applitools-service.d.ts

View file

@ -1,5 +1,9 @@
import logger from '@wdio/logger'
import { Eyes, Target } from '@applitools/eyes-webdriverio'
import type { Services, Capabilities, FunctionProperties } from '@wdio/types'
import type { Browser } from 'webdriverio'
import { ApplitoolsConfig, Frame, Region } from './types'
const log = logger('@wdio/applitools-service')
@ -8,11 +12,11 @@ const DEFAULT_VIEWPORT = {
height: 900
}
export default class ApplitoolsService implements WebdriverIO.ServiceInstance {
export default class ApplitoolsService implements Services.ServiceInstance {
private _isConfigured: boolean = false
private _viewport: Required<ApplitoolsConfig['viewport']>
private _eyes = new Eyes()
private _browser?: WebdriverIO.BrowserObject | WebdriverIO.MultiRemoteBrowser
private _browser?: Browser<'async'>
constructor(private _options: ApplitoolsConfig) {}
@ -37,8 +41,8 @@ export default class ApplitoolsService implements WebdriverIO.ServiceInstance {
this._isConfigured = true
this._eyes.setApiKey(key)
if (this._options.proxy) {
this._eyes.setProxy(this._options.proxy)
if (this._options.eyesProxy) {
this._eyes.setProxy(this._options.eyesProxy)
}
this._viewport = Object.assign({ ...DEFAULT_VIEWPORT }, this._options.viewport)
@ -48,9 +52,9 @@ export default class ApplitoolsService implements WebdriverIO.ServiceInstance {
* set custom commands
*/
before(
caps: WebDriver.Capabilities,
caps: Capabilities.RemoteCapability,
specs: string[],
browser: WebdriverIO.BrowserObject | WebdriverIO.MultiRemoteBrowserObject
browser: Browser<'async'>
) {
this._browser = browser
@ -58,26 +62,29 @@ export default class ApplitoolsService implements WebdriverIO.ServiceInstance {
return
}
this._browser.addCommand('takeSnapshot', (title: string) => {
if (!title) {
throw new Error('A title for the Applitools snapshot is missing')
}
this._browser.addCommand('takeSnapshot', this._takeSnapshot.bind(this))
this._browser.addCommand('takeRegionSnapshot', this._takeRegionSnapshot.bind(this))
}
return this._eyes.check(title, Target.window())
})
_takeSnapshot (title: string): void {
if (!title) {
throw new Error('A title for the Applitools snapshot is missing')
}
this._browser.addCommand('takeRegionSnapshot', (title: string, region: Region, frame: Frame) => {
if (!title) {
throw new Error('A title for the Applitools snapshot is missing')
}
if (!region || region === null) {
throw new Error('A region for the Applitools snapshot is missing')
}
if (!frame) {
return this._eyes.check(title, Target.region(region))
}
return this._eyes.check(title, Target.region(region, frame))
})
return this._eyes.check(title, Target.window())
}
_takeRegionSnapshot (title: string, region: Region, frame: Frame): void {
if (!title) {
throw new Error('A title for the Applitools snapshot is missing')
}
if (!region || region === null) {
throw new Error('A region for the Applitools snapshot is missing')
}
if (!frame) {
return this._eyes.check(title, Target.region(region))
}
return this._eyes.check(title, Target.region(region, frame))
}
beforeTest(test: { title: string, parent: string }) {
@ -105,3 +112,27 @@ export default class ApplitoolsService implements WebdriverIO.ServiceInstance {
this._browser.call(this._eyes.abortIfNotClosed.bind(this._eyes))
}
}
export * from './types'
type ServiceCommands = FunctionProperties<ApplitoolsService>
interface BrowserExtension {
takeSnapshot: ServiceCommands['_takeSnapshot']
takeRegionSnapshot: ServiceCommands['_takeRegionSnapshot']
}
declare global {
namespace WebdriverIO {
interface ServiceOption extends ApplitoolsConfig {}
}
namespace WebdriverIOAsync {
interface Browser extends BrowserExtension { }
interface MultiRemoteBrowser extends BrowserExtension { }
}
namespace WebdriverIOSync {
interface Browser extends BrowserExtension { }
interface MultiRemoteBrowser extends BrowserExtension { }
}
}

View file

@ -0,0 +1,37 @@
interface ProxySettings {
url: string | boolean;
username: string;
password: string;
isHttpOnly: boolean;
}
export interface ApplitoolsConfig {
/**
* Applitools API key to be used. Can be passed via wdio config or via environment
* variable `APPLITOOLS_KEY`
*/
key?: string;
/**
* Applitools server URL to be used.
*/
serverUrl?: string;
/**
* Viewport with which the screenshots should be taken.
*/
viewport?: {
width?: number;
height?: number;
};
/**
* Use proxy for http/https connections with Applitools.
*/
eyesProxy?: ProxySettings;
}
export type Region = {
top: number
left: number
width: number
height: number
} | Element | string;
export type Frame = Element | string;

View file

@ -1,6 +1,11 @@
import type { Capabilities } from '@wdio/types'
import type { ApplitoolsBrowserAsync } from '../src/types'
import ApplitoolsService from '../src'
const caps: WebDriver.Capabilities = {
const expect = global.expect as unknown as jest.Expect
const caps: Capabilities.Capabilities = {
browserName: 'chrome'
}
@ -17,7 +22,7 @@ class BrowserMock {
}
function getBrowser () {
return new BrowserMock() as unknown as WebdriverIO.BrowserObject
return new BrowserMock() as unknown as ApplitoolsBrowserAsync
}
describe('wdio-applitools-service', () => {
@ -74,7 +79,7 @@ describe('wdio-applitools-service', () => {
}
const service = new ApplitoolsService({
key: 'foobar',
proxy: proxyOptions
eyesProxy: proxyOptions
})
service.beforeSession()

View file

@ -3,10 +3,10 @@
"compilerOptions": {
"baseUrl": ".",
"outDir": "./build",
"rootDir": "./src",
"types": ["webdriver", "webdriverio", "@wdio/logger", "jest"]
"rootDir": "./src"
},
"include": [
"src/**/*"
"src/**/*",
"../../@types"
]
}

View file

@ -3,10 +3,10 @@
"compilerOptions": {
"baseUrl": ".",
"outDir": "./build",
"rootDir": "./src",
"types": ["webdriver", "webdriverio", "@wdio/logger", "jest"]
"rootDir": "./src"
},
"include": [
"src/**/*"
"src/**/*",
"../../@types"
]
}

View file

@ -24,8 +24,10 @@
},
"dependencies": {
"@wdio/logger": "6.10.10",
"@wdio/types": "^6.10.6",
"browserstack-local": "^1.4.5",
"got": "^11.0.2"
"got": "^11.0.2",
"webdriverio": "^6.11.3"
},
"peerDependencies": {
"@wdio/cli": "^6.0.1"
@ -33,5 +35,5 @@
"publishConfig": {
"access": "public"
},
"types": "browserstack-service.d.ts"
"types": "./build/index.d.ts"
}

Some files were not shown because too many files have changed in this diff Show more