UNPKG

webdriver

Version:

A Node.js bindings implementation for the W3C WebDriver and Mobile JSONWire Protocol

141 lines (140 loc) 7.01 kB
import logger from '@wdio/logger'; import { commandCallStructure, isValidParameter, getArgumentType } from '@wdio/utils'; import { WebDriverBidiProtocol, } from '@wdio/protocols'; import RequestFactory from './request/factory.js'; const log = logger('webdriver'); const BIDI_COMMANDS = Object.values(WebDriverBidiProtocol).map((def) => def.socket.command); export default function (method, endpointUri, commandInfo, doubleEncodeVariables = false) { const { command, deprecated, ref, parameters, variables = [], isHubCommand = false } = commandInfo; return async function protocolCommand(...args) { const isBidiCommand = BIDI_COMMANDS.includes(command); let endpoint = endpointUri; // clone endpointUri in case we change it const commandParams = [...variables.map((v) => Object.assign(v, { /** * url variables are: */ required: true, // always required as they are part of the endpoint type: 'string' // have to be always type of string })), ...parameters]; const commandUsage = `${command}(${commandParams.map((p) => p.name).join(', ')})`; const moreInfo = `\n\nFor more info see ${ref}\n`; const body = {}; /** * log deprecation warning if command is deprecated */ if (typeof deprecated === 'string') { log.warn(deprecated.replace('This command', `The "${command}" command`)); } /** * Throw this error message for all WebDriver Bidi commands. * In case a successful connection to the browser bidi interface was established, * we attach a custom Bidi prototype to the browser instance. */ if (isBidiCommand) { throw new Error(`Failed to execute WebDriver Bidi command "${command}" as no Bidi session ` + 'was established. Make sure you enable it by setting "webSocketUrl: true" ' + 'in your capabilities and verify that your environment and browser supports it.'); } /** * parameter check */ const minAllowedParams = commandParams.filter((param) => param.required).length; if (args.length < minAllowedParams || args.length > commandParams.length) { const parameterDescription = commandParams.length ? `\n\nProperty Description:\n${commandParams.map((p) => ` "${p.name}" (${p.type}): ${p.description}`).join('\n')}` : ''; throw new Error(`Wrong parameters applied for ${command}\n` + `Usage: ${commandUsage}` + parameterDescription + moreInfo); } /** * parameter type check */ for (const [it, arg] of Object.entries(args)) { if (isBidiCommand) { break; } const i = parseInt(it, 10); const commandParam = commandParams[i]; if (!isValidParameter(arg, commandParam.type)) { /** * ignore if argument is not required */ if (typeof arg === 'undefined' && !commandParam.required) { continue; } const actual = commandParam.type.endsWith('[]') ? `(${(Array.isArray(arg) ? arg : [arg]).map((a) => getArgumentType(a))})[]` : getArgumentType(arg); throw new Error(`Malformed type for "${commandParam.name}" parameter of command ${command}\n` + `Expected: ${commandParam.type}\n` + `Actual: ${actual}` + moreInfo); } /** * inject url variables */ if (i < variables.length) { const encodedArg = doubleEncodeVariables ? encodeURIComponent(encodeURIComponent(arg)) : encodeURIComponent(arg); endpoint = endpoint.replace(`:${commandParams[i].name}`, encodedArg); continue; } /** * rest of args are part of body payload */ body[commandParams[i].name] = arg; } const request = await RequestFactory.getInstance(method, endpoint, body, isHubCommand); request.on('performance', (...args) => this.emit('request.performance', ...args)); this.emit('command', { method, endpoint, body }); log.info('COMMAND', commandCallStructure(command, args)); /** * use then here so we can better unit test what happens before and after the request */ return request.makeRequest(this.options, this.sessionId).then((result) => { if (typeof result.value !== 'undefined') { let resultLog = result.value; if (/screenshot|recording/i.test(command) && typeof result.value === 'string' && result.value.length > 64) { resultLog = `${result.value.slice(0, 61)}...`; } else if (command === 'executeScript' && body.script && body.script.includes('(() => window.__wdioEvents__)')) { resultLog = `[${result.value.length} framework events captured]`; } log.info('RESULT', resultLog); } this.emit('result', { method, endpoint, body, result }); if (command === 'deleteSession') { const shutdownDriver = body.deleteSessionOpts?.shutdownDriver !== false; /** * kill driver process if there is one */ if (shutdownDriver && 'wdio:driverPID' in this.capabilities && this.capabilities['wdio:driverPID']) { log.info(`Kill driver process with PID ${this.capabilities['wdio:driverPID']}`); const killedSuccessfully = process.kill(this.capabilities['wdio:driverPID'], 'SIGKILL'); if (!killedSuccessfully) { log.warn('Failed to kill driver process, manually clean-up might be required'); } setTimeout(() => { /** * clear up potential leaked TLS Socket handles * see https://github.com/puppeteer/puppeteer/pull/10667 */ for (const handle of process._getActiveHandles()) { if (handle.servername && handle.servername.includes('edgedl.me')) { handle.destroy(); } } }, 10); } /** * clear logger stream if session has been terminated */ if (!process.env.WDIO_WORKER_ID) { logger.clearLogger(); } } return result.value; }); }; }