import { logHookError } from './errorHandler.js' import { executeHooksWithArgs, executeAsync } from '../shim.js' import type { WrapperMethods, SpecFunction, BeforeHookParam, AfterHookParam } from './types.js' declare global { // Firstly variable '_wdioDynamicJasmineResultErrorList' gets reference to test result in packages/wdio-jasmine-framework/src/index.ts and then used here in wdio-utils/ as workaround for Jasmine // eslint-disable-next-line @typescript-eslint/no-explicit-any var _wdioDynamicJasmineResultErrorList: any | undefined // eslint-disable-next-line @typescript-eslint/no-explicit-any var _jasmineTestResult: any | undefined } const STACKTRACE_FILTER = [ 'node_modules/webdriver/', 'node_modules/webdriverio/', 'node_modules/@wdio/', '(internal/process/task', '(node:internal/process/task' ] /** * wraps test framework spec/hook function with WebdriverIO before/after hooks * * @param {string} type Test/Step or Hook * @param {object} spec specFn and specFnArgs * @param {object} before beforeFn and beforeFnArgs * @param {object} after afterFn and afterFnArgs * @param {string} cid cid * @param {number} repeatTest number of retries if test fails * @param {string} hookName the hook name * @param {number} timeout the maximum time (in milliseconds) to wait for * @return {*} specFn result */ export const testFnWrapper = function ( this: unknown, ...args: [ string, SpecFunction, BeforeHookParam, AfterHookParam, string, number, string?, number? ] ) { return testFrameworkFnWrapper.call(this, { executeHooksWithArgs, executeAsync }, ...args) } /** * wraps test framework spec/hook function with WebdriverIO before/after hooks * * @param {object} wrapFunctions executeHooksWithArgs, executeAsync, runSync * @param {string} type Test/Step or Hook * @param {object} spec specFn and specFnArgs array * @param {object} before beforeFn and beforeFnArgs function * @param {object} after afterFn and afterFnArgs function * @param {string} cid cid * @param {number} repeatTest number of retries if test fails * @param {string} hookName the hook name * @param {number} timeout the maximum time (in milliseconds) to wait for * @return {*} specFn result */ export const testFrameworkFnWrapper = async function ( this: unknown, { executeHooksWithArgs, executeAsync }: WrapperMethods, type: string, { specFn, specFnArgs }: SpecFunction, { beforeFn, beforeFnArgs }: BeforeHookParam, { afterFn, afterFnArgs }: AfterHookParam, cid: string, repeatTest = 0, hookName?: string, timeout?: number ) { const retries = { attempts: 0, limit: repeatTest } const beforeArgs = beforeFnArgs(this) if (type === 'Hook' && hookName) { beforeArgs.push(hookName) } await logHookError(`Before${type}`, await executeHooksWithArgs(`before${type}`, beforeFn, beforeArgs), cid) let result let error let skip = false const testStart = Date.now() try { result = await executeAsync.call(this, specFn, retries, specFnArgs, timeout) if (globalThis._jasmineTestResult !== undefined) { result = globalThis._jasmineTestResult globalThis._jasmineTestResult = undefined } if (globalThis._wdioDynamicJasmineResultErrorList?.length > 0) { globalThis._wdioDynamicJasmineResultErrorList[0].stack = filterStackTrace(globalThis._wdioDynamicJasmineResultErrorList[0].stack) error = globalThis._wdioDynamicJasmineResultErrorList[0] globalThis._wdioDynamicJasmineResultErrorList = undefined } } catch (_err: unknown) { /** * To address skipping tests for Mocha and Jasmine */ if (!(JSON.stringify(_err, Object.getOwnPropertyNames(_err)).includes('sync skip; aborting execution') || JSON.stringify(_err, Object.getOwnPropertyNames(_err)).includes('marked Pending'))) { const err = _err instanceof Error ? _err : new Error(typeof _err === 'string' ? _err : 'An unknown error occurred') if (err.stack) { err.stack = filterStackTrace(err.stack) } error = err } else { skip = true } } const duration = Date.now() - testStart const afterArgs = afterFnArgs(this) afterArgs.push({ retries, error, result, duration, passed: !error && !skip, skipped: skip }) if (type === 'Hook' && hookName) { afterArgs.push(hookName) } await logHookError(`After${type}`, await executeHooksWithArgs(`after${type}`, afterFn, [...afterArgs]), cid) if (error && !error.matcherName) { throw error } return result } /** * Filter out internal stacktraces. exporting to allow testing of the function * @param {string} stack Stacktrace * @returns {string} */ export const filterStackTrace = (stack: string): string => { return stack .split('\n') .filter(line => !STACKTRACE_FILTER.some(l => line.includes(l))) .map(line => line.replace(/\?invalidateCache=(\d\.\d+|\d)/g, '')) .join('\n') }