import { type local, type remote } from 'webdriver' import { SCRIPT_PREFIX, SCRIPT_SUFFIX } from '../../commands/constant.js' export class WebdriverBidiExeception extends Error { #params: remote.ScriptCallFunctionParameters #result: local.ScriptEvaluateResultException constructor ( params: remote.ScriptCallFunctionParameters, result: local.ScriptEvaluateResultException ) { super(result.exceptionDetails.text) this.name = 'WebdriverBidiExeception' this.#params = params this.#result = result this.stack = this.#getCustomStack() } #getCustomStack () { const origStack = this.stack /** * try to detect the line that caused the error and add it to the stack trace */ const failureLine = this.#getFailureLine() const stack: string[] = origStack?.split('\n') || [] const wrapCommandIndex = stack.findLastIndex((line) => line.includes('Context.executeAsync')) const executeLine = stack[wrapCommandIndex - 1] as string | undefined if (failureLine && executeLine) { const line = executeLine.replace('file://', '').split(':') const row = line.length > 3 ? line[2] : line[1] const [errorMessage, ...restOfStack] = stack const linePrefix = ` ${row} │ ` const codeLine = [ linePrefix + failureLine, ' '.repeat(linePrefix.length - 2) + '╵ ' + '~'.repeat(failureLine.length), '' ] return [errorMessage, executeLine, ...codeLine, ...restOfStack].join('\n') } return origStack } /** * This is an attempt to identify the snippet of code that caused an execute(Async) function to * throw an exception * @param {string} script script that executed in the browser * @param {number} columnNumber column in which the scrpt threw an exception * @returns the line of failure in which the code threw an exception or `undefined` if we could not find it */ #getFailureLine () { const script = this.#params.functionDeclaration const exceptionDetails =this.#result.exceptionDetails const userScript = script.split('\n').find((l) => l.includes(SCRIPT_PREFIX)) if (!userScript) { return } let length = 0 /** * Detect whether `tsx` minified the code */ const isMinified = script.split('\n').some((line) => line.includes(SCRIPT_PREFIX) && line.includes(SCRIPT_SUFFIX)) if (isMinified) { /** * If the user tests is a TypeScript file the code passed into execute is compiled via `tsx` * which uses Esbuild. It transforms whole function block into a single line separated by ";" * With this loop we identify the section that caused the error */ for (const line of userScript.split(';')) { if ((length + line.length) >= exceptionDetails.columnNumber) { return line.includes(SCRIPT_SUFFIX) ? line.slice(0, line.indexOf(SCRIPT_SUFFIX)) : line } length += line.length } } else { const slicedScript = script.slice( script.indexOf(SCRIPT_PREFIX) + SCRIPT_PREFIX.length, script.indexOf(SCRIPT_SUFFIX) ) const lineDiff = 9 const line = slicedScript.split('\n')[exceptionDetails.lineNumber - lineDiff]?.slice(exceptionDetails.columnNumber) return line } return undefined } }